aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes/(app)/[username]
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2024-09-24 20:12:43 -0700
committerAdam Soltys <[email protected]>2024-09-24 20:12:43 -0700
commitd49d4119811dee9270f0858f3f03e5d8cd0d95ba (patch)
tree42101a3ccf47f7b7290637700b90a843476e56f7 /src/routes/(app)/[username]
parent20c0c52f8b850bfbab490735b0b5cd5d08851488 (diff)
parent9531ccbdf01b07c392b1b5cb6d8ad4680ee29d2c (diff)
downloadcoinos-ui-d49d4119811dee9270f0858f3f03e5d8cd0d95ba.tar.gz
coinos-ui-d49d4119811dee9270f0858f3f03e5d8cd0d95ba.zip
Merge branch 'self-custody'
Diffstat (limited to 'src/routes/(app)/[username]')
-rw-r--r--src/routes/(app)/[username]/(profile)/+layout.svelte70
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.server.ts6
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte258
-rw-r--r--src/routes/(app)/[username]/+layout.server.ts11
-rw-r--r--src/routes/(app)/[username]/+layout.svelte50
-rw-r--r--src/routes/(app)/[username]/address/+page.server.ts9
-rw-r--r--src/routes/(app)/[username]/address/+page.svelte0
-rw-r--r--src/routes/(app)/[username]/card/[serial]/+page.server.ts16
-rw-r--r--src/routes/(app)/[username]/card/[serial]/+page.svelte36
-rw-r--r--src/routes/(app)/[username]/customers/+page.svelte0
-rw-r--r--src/routes/(app)/[username]/funds/+page.server.ts0
-rw-r--r--src/routes/(app)/[username]/funds/+page.svelte52
-rw-r--r--src/routes/(app)/[username]/generate/+page.svelte60
-rw-r--r--src/routes/(app)/[username]/generate/+server.ts12
-rw-r--r--src/routes/(app)/[username]/invoice/+server.ts7
-rw-r--r--src/routes/(app)/[username]/invoice/[id]/+layout.server.ts39
-rw-r--r--src/routes/(app)/[username]/invoice/[id]/+layout.svelte1
-rw-r--r--src/routes/(app)/[username]/invoice/[id]/+page.server.ts5
-rw-r--r--src/routes/(app)/[username]/invoice/[id]/memo/+page.server.ts13
-rw-r--r--src/routes/(app)/[username]/invoice/[id]/memo/+page.svelte95
-rw-r--r--src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte85
-rw-r--r--src/routes/(app)/[username]/invoice/[id]/tip/+page.server.ts12
-rw-r--r--src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte209
-rw-r--r--src/routes/(app)/[username]/invoice/sent/+page.svelte11
-rw-r--r--src/routes/(app)/[username]/items/[id]/+page.server.ts21
-rw-r--r--src/routes/(app)/[username]/items/[id]/+page.svelte18
-rw-r--r--src/routes/(app)/[username]/items/create/+page.server.ts16
-rw-r--r--src/routes/(app)/[username]/items/create/+page.svelte19
-rw-r--r--src/routes/(app)/[username]/map/+page.server.ts12
-rw-r--r--src/routes/(app)/[username]/map/+page.svelte8
-rw-r--r--src/routes/(app)/[username]/messages/+page.server.ts42
-rw-r--r--src/routes/(app)/[username]/messages/+page.svelte92
-rw-r--r--src/routes/(app)/[username]/messages/[recipient]/+page.server.ts24
-rw-r--r--src/routes/(app)/[username]/messages/[recipient]/+page.svelte191
-rw-r--r--src/routes/(app)/[username]/payments/[...page]/+page.server.ts26
-rw-r--r--src/routes/(app)/[username]/payments/[...page]/+page.svelte315
-rw-r--r--src/routes/(app)/[username]/payments/[...page]/+server.ts18
-rw-r--r--src/routes/(app)/[username]/receive/+page.svelte (renamed from src/routes/(app)/[username]/invoice/[id]/+page.svelte)150
-rw-r--r--src/routes/(app)/[username]/receive/[...param]/+page.server.ts20
-rw-r--r--src/routes/(app)/[username]/receive/[...param]/+page.svelte116
-rw-r--r--src/routes/(app)/[username]/reset/+server.ts10
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/+page.server.ts34
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/+page.svelte257
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/_account.svelte190
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/_pos.svelte238
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/_security.svelte321
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte92
-rw-r--r--src/routes/(app)/[username]/verify/[code]/+page.server.ts14
-rw-r--r--src/routes/(app)/[username]/verify/[code]/+page.svelte35
49 files changed, 127 insertions, 3209 deletions
diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte
index a213e792..30a6e325 100644
--- a/src/routes/(app)/[username]/(profile)/+layout.svelte
+++ b/src/routes/(app)/[username]/(profile)/+layout.svelte
@@ -82,13 +82,12 @@
<div class="container mx-auto w-full px-4 flex flex-wrap lg:flex-nowrap">
<div class="hidden lg:block lg:w-[280px] xl:w-[360px]" />
<div
- class="w-full lg:w-[280px] xl:w-[360px] lg:absolute space-y-5 left-20 mx-auto mb-5"
+ class="w-full lg:w-[280px] xl:w-[360px] lg:absolute space-y-2 left-20 mx-auto"
>
- <div
- class="flex text-3xl font-bold text-center mx-auto justify-center gap-2"
- >
- <div class="my-auto break-words w-full">{display || username}</div>
- </div>
+ <button type="button" on:click={toggleDetails} class="flex text-3xl font-bold text-center mx-auto justify-center gap-2">
+ <div class="my-auto break-words">{display || username}</div>
+ <Icon icon="qr" style="w-8 invert my-auto" />
+ </button>
{#if subject.address}
<div
@@ -112,68 +111,51 @@
<span class="text-secondary">{$t("user.followers")}</span></a
>
</div>
-
- <button
- type="button"
- on:click={toggleDetails}
- class="flex text-base text-secondary mx-auto"
- >
- <div class="mt-1">
- {$t("user.moreDetails")}
- </div>
- <div
- class:-rotate-90={showDetails}
- class:rotate-180={!showDetails}
- class="my-auto"
- >
- <Icon icon="chevron-left" style="w-6 mr-2" />
- </div>
- </button>
</div>
{#if showDetails}
- <div class="text-xl space-y-5">
+ <div class="space-y-5 pb-20">
<div>
- <div class="font-bold">{$t("user.profilePage")}</div>
+ <div class="text-secondary">{$t("user.lightningAddress")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
- {profile}
+ <div class="break-all grow text-xl">
+ {lnaddr}
</div>
<div class="flex mb-auto gap-1">
- <button class="my-auto" on:click={() => copy(profile)}
+ <button class="my-auto" on:click={() => copy(lnaddr)}
><Icon icon="copy" style="max-w-max w-8 min-w-[32px]" /></button
>
- <a
- href={`/qr/${encodeURIComponent(
- `${$page.url.protocol}//${profile}`
- )}`}
- class="my-auto"
- >
+ <a href={`/qr/${encodeURIComponent(lnaddr)}`} class="my-auto">
<Icon icon="qr" style="invert max-w-max min-w-[32px]" />
</a>
</div>
</div>
</div>
<div>
- <div class="font-bold">{$t("user.lightningAddress")}</div>
+ <div class="text-secondary">{$t("user.url")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
- {lnaddr}
+ <div class="break-all grow text-xl">
+ {profile}
</div>
<div class="flex mb-auto gap-1">
- <button class="my-auto" on:click={() => copy(lnaddr)}
+ <button class="my-auto" on:click={() => copy(profile)}
><Icon icon="copy" style="max-w-max w-8 min-w-[32px]" /></button
>
- <a href={`/qr/${encodeURIComponent(lnaddr)}`} class="my-auto">
+ <a
+ href={`/qr/${encodeURIComponent(
+ `${$page.url.protocol}//${profile}`
+ )}`}
+ class="my-auto"
+ >
<Icon icon="qr" style="invert max-w-max min-w-[32px]" />
</a>
</div>
</div>
</div>
<div>
- <div class="font-bold">{$t("user.nostrPubkey")}</div>
+ <div class="text-secondary">{$t("user.nostrPubkey")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
+ <div class="break-all grow text-xl">
{npub}
</div>
<div class="flex my-auto gap-1">
@@ -188,9 +170,9 @@
</div>
{#if user.id === subject.id}
<div>
- <div class="font-bold">{$t("user.nwc")}</div>
+ <div class="text-secondary">{$t("user.nwc")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
+ <div class="break-all grow text-xl">
{nwc}
</div>
<div class="flex my-auto gap-1">
@@ -237,7 +219,7 @@
{#if user && user.username !== subject.username && subject.pubkey}
<a
- href={`/${user.username}/messages/${subject.username}`}
+ href={`/messages/${subject.username}`}
class="contents"
>
<button
diff --git a/src/routes/(app)/[username]/(profile)/+page.server.ts b/src/routes/(app)/[username]/(profile)/+page.server.ts
index 59eb44d5..ed4ee58c 100644
--- a/src/routes/(app)/[username]/(profile)/+page.server.ts
+++ b/src/routes/(app)/[username]/(profile)/+page.server.ts
@@ -1,11 +1,15 @@
import { auth, get } from "$lib/utils";
export async function load({ cookies, depends, parent }) {
+ depends("app:payments");
depends("app:items");
let { subject } = await parent();
+
+ let accounts = await get("/accounts", auth(cookies));
+
let items = await get(`/${subject.id}/items`, auth(cookies));
items.map((i) => (i.quantity = 0));
- return { items };
+ return { accounts, items };
}
diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte
index 32e6807a..399a33e9 100644
--- a/src/routes/(app)/[username]/(profile)/+page.svelte
+++ b/src/routes/(app)/[username]/(profile)/+page.svelte
@@ -1,161 +1,54 @@
<script>
- import { browser } from "$app/environment";
- import { goto, invalidate } from "$app/navigation";
- import { btc, f, sat, post } from "$lib/utils";
+ import { btc, f, sat } from "$lib/utils";
import Icon from "$comp/Icon.svelte";
+ import Account from "$comp/Account.svelte";
import Balance from "$comp/Balance.svelte";
- import DeleteItem from "$comp/DeleteItem.svelte";
+ import Items from "$comp/Items.svelte";
import { t } from "$lib/translations";
- import { fade } from "svelte/transition";
- import { cubicIn } from "svelte/easing";
- import { flip } from "svelte/animate";
import { installPrompt } from "$lib/store";
- import {
- dndzone,
- SHADOW_ITEM_MARKER_PROPERTY_NAME,
- SOURCES,
- TRIGGERS,
- } from "svelte-dnd-action";
-
export let data;
- let flipDurationMs = 200;
- let dropTargetStyle = {};
-
- let { subject, user, items, rates } = data;
+ let { accounts, subject, user, items, rates } = data;
$: ({ currency } = subject);
$: rate = rates[currency];
- $: refresh(data);
- let refresh = (d) => ({ items, subject, user, rates } = d);
- let dragDisabled =
- subject?.id !== user?.id || (browser && window.innerWidth < 768);
-
- let deleting;
$: total = items.reduce((a, b) => a + b.price * b.quantity, 0);
- let del = (item) => {
- deleting = item;
- };
-
- function handleConsider(e) {
- const {
- items: newItems,
- info: { source, trigger },
- } = e.detail;
- items = newItems;
- if (source === SOURCES.KEYBOARD && trigger === TRIGGERS.DRAG_STOPPED)
- reset();
- }
-
- async function handleFinalize(e) {
- const {
- items: newItems,
- info: { source },
- } = e.detail;
- items = newItems;
- if (source === SOURCES.POINTER) reset();
-
- if (subject?.id === user?.id) {
- await post("/api/items/sort", { items });
- invalidate("app:items");
- }
- }
-
- function startDrag(e) {
- e.preventDefault();
- dragDisabled = false;
- }
-
- function handleKeyDown(e) {
- if ((e.key === "Enter" || e.key === " ") && dragDisabled)
- dragDisabled = false;
- }
-
- $: if (total > 0 && browser) {
- let payButton = document.querySelector("#payButton");
- if (payButton) payButton.addEventListener("click", checkout);
- }
-
- let checkout = async () => {
- let invoice = {
- type: "lightning",
- amount: btc(total, rate),
- currency,
- items: items.filter((i) => i.quantity > 0),
- prompt: subject.prompt,
- memoPrompt: subject.memoPrompt,
- rate,
- };
-
- let { id } = await post(`/${subject.username}/invoice`, {
- invoice,
- user: { username: subject.username },
- });
-
- if (invoice.memoPrompt) {
- return goto(`/${subject.username}/invoice/${id}/memo`);
- }
- goto(`/${subject.username}/invoice/${id}`);
- };
- let reset = () =>
- (dragDisabled =
- user?.username !== subject?.username || window.innerWidth < 768);
+ let refresh = (d) => ({ accounts, items, subject, user, rates } = d);
+ $: refresh(data);
let install = async () => {
if (!$installPrompt) return;
await $installPrompt.prompt();
$installPrompt = null;
};
-</script>
-
-<svelte:window on:resize={reset} />
-{#if deleting}
- <DeleteItem bind:item={deleting} />
-{/if}
+ if (user) user.savings = 0;
+</script>
-<div class="space-y-5">
+<div class="space-y-2">
{#if user?.id === subject.id}
- <div class="flex justify-center lg:justify-start">
- <Balance {user} {rate} />
- </div>
-
- {#if !user.balance}
- <div class="mb-8">
- <p class="text-secondary text-lg">
- {$t("user.welcome")}
- </p>
- </div>
- {/if}
-
- <div class="flex flex-wrap gap-3 justify-center w-full text-xl">
- <a href={`/send`} class="contents grow">
- <button
- type="button"
- class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow"
- >
- <div class="mx-auto flex gap-2">
- <Icon icon="send" style="w-8" />
- <div class="my-auto">{$t("user.dashboard.send")}</div>
- </div>
- </button>
- </a>
+ {#each accounts as account}
+ <Account {user} {rate} {account} />
+ {/each}
- <a href={`/${user.username}/receive`} class="contents">
+ <div class="flex justify-center gap-2">
+ <a href={`/account/savings`} class="contents">
<button
- class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow"
+ class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex w-full justify-center"
>
<div class="mx-auto flex gap-2">
- <Icon icon="receive" style="w-8" />
- <div class="my-auto">{$t("user.dashboard.receive")}</div>
+ <Icon icon="plus" style="w-8 mx-auto" />
+ <div class="my-auto text-xl whitespace-nowrap">
+ {$t("accounts.addAccount")}
+ </div>
</div>
</button>
</a>
{#if $installPrompt}
<button
- class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white grow basis-full fixed bottom-16 xl:hidden"
+ class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white xl:hidden w-full justify-center"
on:click={install}
>
<div class="mx-auto flex gap-2">
@@ -169,115 +62,8 @@
</div>
{/if}
- {#if items.length}
- <h2 class="text-2xl font-bold">Select items</h2>
- {/if}
- <div
- class="grid sm:grid-cols-2 gap-4 pb-20"
- use:dndzone={{ items, flipDurationMs, dropTargetStyle, dragDisabled }}
- on:consider={handleConsider}
- on:finalize={handleFinalize}
- >
- {#each items as i (i.id)}
- <button
- type="button"
- class="w-full text-center cursor-pointer hover:opacity-80 mb-auto"
- on:click={() => i.quantity++}
- >
- <div class="relative w-full h-64 overflow-hidden rounded-xl">
- {#if i[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
- <div
- in:fade={{ duration: 200, easing: cubicIn }}
- class="custom-shadow-item"
- />
- {:else if i.image}
- <img
- src={`/api/public/${i.image}.webp`}
- alt={i.name}
- class="mx-auto object-cover h-full w-full"
- />
- {:else}
- <div
- class="bg-gradient-to-r from-primary to-gradient mb-4 cursor-pointer hover:opacity-80 w-full h-full"
- alt="Banner"
- />
- {/if}
- {#if user?.username === subject.username}
- <div class="flex gap-2 justify-center absolute top-2 right-2">
- <button type="button" on:click|stopPropagation={() => {}}>
- <a href={`/${user.username}/items/${i.id}`}>
- <button
- class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
- >
- <Icon icon="edit" style="w-8 mx-auto invert" />
- </button>
- </a>
- </button>
- <button
- class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
- on:click|stopPropagation={() => del(i)}
- >
- <Icon icon="trash" style="w-8 mx-auto invert" />
- </button>
- <button
- type="button"
- on:click|stopPropagation={() => {}}
- class="sm:hidden"
- on:mousedown={startDrag}
- on:touchstart={startDrag}
- on:keydown={handleKeyDown}
- >
- <a href={`/${user.username}/items/${i.id}`}>
- <button
- class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
- >
- <Icon icon="move" style="w-8 mx-auto invert" />
- </button>
- </a>
- </button>
- </div>
- {/if}
- </div>
- <div class="bg-white rounded-xl py-2">
- <div class="flex text-left gap-2">
- <div class="overflow-hidden space-y-1">
- <div class="text-2xl break-words">{i.name}</div>
- <div class="flex gap-2">
- <div class="font-semibold text-lg">
- {f(i.price, currency)}
- </div>
- <div class="text-secondary my-auto">
- {sat(btc(i.price, rate))}
- </div>
- </div>
- </div>
- <div class="flex gap-1 ml-auto mb-auto justify-around tabular-nums">
- {#if i.quantity}
- <button
- type="button"
- on:click|stopPropagation={() =>
- i.quantity > 0 && i.quantity--}
- >
- <Icon icon="minus" style="w-10 min-w-10" />
- </button>
- {/if}
- <div class="text-2xl my-auto text-center">
- {#if i.quantity}{i.quantity}{:else}&mdash;{/if}
- </div>
- {#if i.quantity}
- <button
- type="button"
- on:click|stopPropagation={() => i.quantity++}
- >
- <Icon icon="plus" style="w-10 min-w-10" />
- </button>
- {/if}
- </div>
- </div>
- </div>
- </button>
- {/each}
- </div>
+ <Items {items} {subject} {user} {total} {currency} {rate} />
+
<div
class="fixed inset-x-0 mx-auto flex bottom-16"
class:static={!items.length}
diff --git a/src/routes/(app)/[username]/+layout.server.ts b/src/routes/(app)/[username]/+layout.server.ts
index 11e6d45c..9a520036 100644
--- a/src/routes/(app)/[username]/+layout.server.ts
+++ b/src/routes/(app)/[username]/+layout.server.ts
@@ -1,17 +1,24 @@
import { get } from "$lib/utils";
import { error } from "@sveltejs/kit";
-export let load = async ({ cookies, depends, params: { username } }) => {
+export let load = async ({
+ cookies,
+ depends,
+ params: { username },
+ parent,
+}) => {
depends("app:user");
try {
let subject = await get(`/users/${username}`);
+ let { user } = await parent();
+ if (!user) user = subject;
let expires = new Date();
expires.setSeconds(expires.getSeconds() + 380 * 24 * 60 * 60);
cookies.set("username", username, { path: "/", expires });
- return { subject };
+ return { subject, user };
} catch (e) {
error(500, "Unable to retrieve user account data");
}
diff --git a/src/routes/(app)/[username]/+layout.svelte b/src/routes/(app)/[username]/+layout.svelte
index dd29e3aa..ce152831 100644
--- a/src/routes/(app)/[username]/+layout.svelte
+++ b/src/routes/(app)/[username]/+layout.svelte
@@ -1,57 +1,9 @@
<script>
- import AppHeader from "$comp/AppHeader.svelte";
- import { onNavigate } from "$app/navigation";
export let data;
-
- onNavigate((navigation) => {
- if (!document.startViewTransition) return;
-
- return new Promise((resolve) => {
- document.startViewTransition(async () => {
- resolve();
- await navigation.complete;
- });
- });
- });
</script>
-<AppHeader {data} />
<div class="content">
<slot />
</div>
-<style>
- @keyframes fade-in {
- from {
- opacity: 0;
- }
- }
-
- @keyframes fade-out {
- to {
- opacity: 0;
- }
- }
-
- @keyframes slide-from-right {
- from {
- transform: translateX(60px);
- }
- }
-
- @keyframes slide-to-left {
- to {
- transform: translateX(-60px);
- }
- }
-
- :root::view-transition-old(root) {
- animation: 280ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
- 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
- }
-
- :root::view-transition-new(root) {
- animation: 280ms cubic-bezier(0, 0, 0.2, 1) 80ms both fade-in,
- 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
- }
-</style>
+<slot name="fixed" />
diff --git a/src/routes/(app)/[username]/address/+page.server.ts b/src/routes/(app)/[username]/address/+page.server.ts
deleted file mode 100644
index 2f7841ab..00000000
--- a/src/routes/(app)/[username]/address/+page.server.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { auth, post } from "$lib/utils";
-import { redirect } from "@sveltejs/kit";
-
-export async function load({ cookies, parent }) {
- let user = (await parent()).subject;
- let invoice = { type: "bitcoin" };
- let { id } = await post(`/invoice`, { invoice, user }, auth(cookies));
- redirect(307, `/${user.username}/invoice/${id}`);
-}
diff --git a/src/routes/(app)/[username]/address/+page.svelte b/src/routes/(app)/[username]/address/+page.svelte
deleted file mode 100644
index e69de29b..00000000
--- a/src/routes/(app)/[username]/address/+page.svelte
+++ /dev/null
diff --git a/src/routes/(app)/[username]/card/[serial]/+page.server.ts b/src/routes/(app)/[username]/card/[serial]/+page.server.ts
deleted file mode 100644
index 945eee30..00000000
--- a/src/routes/(app)/[username]/card/[serial]/+page.server.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { auth, get } from "$lib/utils";
-import { redirect } from "@sveltejs/kit";
-
-export let load = async ({ cookies, params }) => {
- let { serial } = params;
- let fund;
- try {
- fund = await get(`/fund/${serial}`, auth(cookies));
- } catch (e) {
- console.log(e);
- }
-
- if (fund) redirect(307, `/fund/${serial}`);
-
- return params;
-};
diff --git a/src/routes/(app)/[username]/card/[serial]/+page.svelte b/src/routes/(app)/[username]/card/[serial]/+page.svelte
deleted file mode 100644
index 1d11ebee..00000000
--- a/src/routes/(app)/[username]/card/[serial]/+page.svelte
+++ /dev/null
@@ -1,36 +0,0 @@
-<script>
- import Card from "$comp/Card.svelte";
- import Icon from "$comp/Icon.svelte";
- export let data;
- let { serial } = data;
- let balance = 0;
-</script>
-
-<div class="space-y-5">
- <div class="relative flex justify-center gap-2">
- <h1 class="text-2xl md:text-3xl font-semibold my-auto">Card {serial}</h1>
- </div>
- <div class="flex justify-center">
- <a href={`/send/pot/${serial}`}>
- <button
- class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60 bg-black text-white justify-center gap-2"
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- class="invert text-black peer-placeholder-shown:text-gray-300 h-6 w-6 my-auto"
- fill="none"
- viewBox="0 0 24 24"
- stroke="currentColor"
- >
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"
- />
- </svg>
- <div class="my-auto">Add Funds</div>
- </button>
- </a>
- </div>
-</div>
diff --git a/src/routes/(app)/[username]/customers/+page.svelte b/src/routes/(app)/[username]/customers/+page.svelte
deleted file mode 100644
index e69de29b..00000000
--- a/src/routes/(app)/[username]/customers/+page.svelte
+++ /dev/null
diff --git a/src/routes/(app)/[username]/funds/+page.server.ts b/src/routes/(app)/[username]/funds/+page.server.ts
deleted file mode 100644
index e69de29b..00000000
--- a/src/routes/(app)/[username]/funds/+page.server.ts
+++ /dev/null
diff --git a/src/routes/(app)/[username]/funds/+page.svelte b/src/routes/(app)/[username]/funds/+page.svelte
deleted file mode 100644
index 25686e86..00000000
--- a/src/routes/(app)/[username]/funds/+page.svelte
+++ /dev/null
@@ -1,52 +0,0 @@
-<script>
- import Avatar from "$comp/Avatar.svelte";
- import Icon from "$comp/Icon.svelte";
- import { t } from "$lib/translations";
-
- export let data;
-
- let { funds } = data;
-</script>
-
-<div class="mt-24 mb-20">
- <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10">
- Funds
- </h1>
-
- <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5">
- <div class="text-base">
- <div class="grid grid-cols-3 border-b h-24 hover:bg-gray-100 px-4">
- <div class="whitespace-nowrap my-auto">
- <div class="text-secondary" />
- </div>
-
- <div class="flex my-auto">
- <div class="text-secondary flex">
- <div class="my-auto mr-1">
- <img src="/images/moneypot.png" class="w-12" alt="Pot" />
- </div>
-
- <div class="my-auto">Pot</div>
- </div>
- </div>
-
- <div class="text-secondary text-right text-sm my-auto">
- <div />
- </div>
- </div>
- </div>
-
- <button
- class="ml-auto rounded-full border py-2 px-4 w-36 hover:opacity-80 flex mx-auto"
- >
- <Icon icon="plus" style="opacity-50 mr-2 my-auto" />
- <div class="my-auto">{$t("payments.create")}</div>
- </button>
- </div>
-</div>
-
-<style>
- .active * {
- @apply bg-black text-white border-black;
- }
-</style>
diff --git a/src/routes/(app)/[username]/generate/+page.svelte b/src/routes/(app)/[username]/generate/+page.svelte
deleted file mode 100644
index 767b7545..00000000
--- a/src/routes/(app)/[username]/generate/+page.svelte
+++ /dev/null
@@ -1,60 +0,0 @@
-<script>
- import { fail, post, wait } from "$lib/utils";
- import { onMount } from "svelte";
- import { goto, invalidate } from "$app/navigation";
- import Pin from "$comp/Pin.svelte";
- import Spinner from "$comp/Spinner.svelte";
- import { loginRedirect, pin, password } from "$lib/store";
- import { generate } from "$lib/nostr";
- import { browser } from "$app/environment";
-
- export let data;
- let { user } = data;
-
- let loaded;
- let gen = async () => {
- if (!browser) return;
-
- if (user.pubkey) {
- goto($loginRedirect || `/${user.username}`, { invalidateAll: true });
- return;
- }
-
- try {
- await generate(user);
- } catch (e) {
- console.log("error generating", e);
- }
-
- try {
- user.pin = $pin;
- await post(`/${user.username}/generate`, user);
- goto($loginRedirect || `/${user.username}`, { invalidateAll: true });
- } catch (e) {
- $pin = "";
- if (e.message?.startsWith("Pin")) {
- fail("Wrong pin, try again");
- } else {
- fail("Failed to generate keys");
- goto("/");
- }
- }
- };
-
- onMount(() => browser && setTimeout(() => (loaded = true) && gen(), 50));
-
- $: $pin?.length === 6 && gen(user);
-</script>
-
-{#if loaded && user?.haspin && $pin?.length !== 6}
- <Pin />
-{/if}
-
-<div class="container px-4 text-center mx-auto">
- <h1 class="text-3xl md:text-4xl font-bold mb-6">
- Generating new account keys
- </h1>
- <div class="flex w-full py-5 max-w-[200px] mx-auto bg-black rounded-2xl">
- <Spinner />
- </div>
-</div>
diff --git a/src/routes/(app)/[username]/generate/+server.ts b/src/routes/(app)/[username]/generate/+server.ts
deleted file mode 100644
index 5a442982..00000000
--- a/src/routes/(app)/[username]/generate/+server.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { auth, post } from "$lib/utils";
-import { error, json } from "@sveltejs/kit";
-
-export async function POST({ cookies, request }) {
- try {
- let body = await request.json();
- return json(await post(`/user`, body, auth(cookies)));
- } catch (e: any) {
- console.log(e);
- error(500, e.message);
- }
-}
diff --git a/src/routes/(app)/[username]/invoice/+server.ts b/src/routes/(app)/[username]/invoice/+server.ts
deleted file mode 100644
index 5e3d0300..00000000
--- a/src/routes/(app)/[username]/invoice/+server.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { auth, post } from "$lib/utils";
-import { json } from "@sveltejs/kit";
-
-export let POST = async ({ cookies: c, request: r }) => {
- let j = await r.json();
- return json(await post("/invoice", j, auth(c)));
-};
diff --git a/src/routes/(app)/[username]/invoice/[id]/+layout.server.ts b/src/routes/(app)/[username]/invoice/[id]/+layout.server.ts
deleted file mode 100644
index 41554c9f..00000000
--- a/src/routes/(app)/[username]/invoice/[id]/+layout.server.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import Qr from "qrcode-base64";
-import { get } from "$lib/utils";
-import { redirect } from "@sveltejs/kit";
-
-export async function load({ depends, params, url, parent }) {
- depends("app:invoice");
-
- let { subject, user } = await parent();
- let { id } = params;
- let invoice = await get(`/invoice/${id}`);
- let options = !!url.searchParams.get("options");
-
- if (
- user &&
- invoice.uid !== user.id &&
- !(url.pathname.includes("tip") || url.pathname.includes("memo")) &&
- !options
- ) {
- redirect(307, `/send/invoice/${id}`);
- }
-
- let { amount, pending, received } = invoice;
- amount = parseInt(amount);
-
- let paid =
- (!amount && (pending || received)) ||
- (amount > 0 && (pending >= amount || received >= amount));
- if (paid && !url.pathname.endsWith("paid")) {
- redirect(
- 307,
- `/${params.username}/invoice/${id}/paid` +
- (options ? "?options=true" : "")
- );
- }
-
- let src = Qr.drawImg(invoice.text || "", { size: 340 });
-
- return { id, invoice, subject, src };
-}
diff --git a/src/routes/(app)/[username]/invoice/[id]/+layout.svelte b/src/routes/(app)/[username]/invoice/[id]/+layout.svelte
deleted file mode 100644
index 4fa864ce..00000000
--- a/src/routes/(app)/[username]/invoice/[id]/+layout.svelte
+++ /dev/null
@@ -1 +0,0 @@
-<slot />
diff --git a/src/routes/(app)/[username]/invoice/[id]/+page.server.ts b/src/routes/(app)/[username]/invoice/[id]/+page.server.ts
deleted file mode 100644
index a57efa64..00000000
--- a/src/routes/(app)/[username]/invoice/[id]/+page.server.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import invoice from "$lib/invoice";
-
-export const actions = {
- default: invoice,
-};
diff --git a/src/routes/(app)/[username]/invoice/[id]/memo/+page.server.ts b/src/routes/(app)/[username]/invoice/[id]/memo/+page.server.ts
deleted file mode 100644
index 58d56018..00000000
--- a/src/routes/(app)/[username]/invoice/[id]/memo/+page.server.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import invoice from "$lib/invoice";
-import { redirect } from "@sveltejs/kit";
-
-export async function load({ parent, params }) {
- let { invoice } = await parent();
- if (!invoice.amount) {
- redirect(307, `/${params.username}/invoice/${invoice.id}`);
- }
-}
-
-export const actions = {
- default: invoice,
-};
diff --git a/src/routes/(app)/[username]/invoice/[id]/memo/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/memo/+page.svelte
deleted file mode 100644
index 4d23ff19..00000000
--- a/src/routes/(app)/[username]/invoice/[id]/memo/+page.svelte
+++ /dev/null
@@ -1,95 +0,0 @@
-<script>
- import { onMount } from "svelte";
- import { browser } from "$app/environment";
- import { invoice as inv, request } from "$lib/store";
- import {
- copy,
- select,
- f,
- sat,
- get,
- reverseFormat,
- s,
- sats,
- } from "$lib/utils";
- import Icon from "$comp/Icon.svelte";
- import { t } from "$lib/translations";
- import { enhance } from "$app/forms";
-
- let submitting;
- let submit;
-
- export let data;
- $: refresh(data);
- let { invoice, id, user, rates } = data;
- let {
- amount,
- hash,
- items,
- memoPrompt,
- type,
- rate,
- received,
- prompt,
- text,
- tip,
- user: { username, currency },
- } = invoice;
-
- invoice.memo = user?.username;
-
- let refresh = (data) => {
- $inv = null;
-
- ({ invoice, id } = data);
- ({
- amount,
- hash,
- items,
- memoPrompt,
- type,
- received,
- prompt,
- text,
- tip,
- user: { username },
- } = invoice);
- };
-</script>
-
-<form
- method="POST"
- use:enhance
- class:invisible={submitting}
- class="container px-4 max-w-lg mx-auto space-y-5"
->
- <input type="hidden" name="amount" value={amount} />
- <input type="hidden" name="tip" value={tip} />
- <input type="hidden" name="username" value={username} />
- <input type="hidden" name="rate" value={invoice.rate} />
- <input type="hidden" name="memoPrompt" value={invoice.memoPrompt} />
- <input type="hidden" name="prompt" value={invoice.prompt} />
- <input type="hidden" name="type" value={type} />
- <input type="hidden" name="hash" value={hash} />
- <input type="hidden" name="items" value={JSON.stringify(items)} />
-
- <h1 class="text-4xl font-semibold text-center">{$t("invoice.addMemo")}</h1>
- <div class="space-y-2">
- <input
- use:select
- name="memo"
- class="w-full p-4 border rounded-xl text-xl"
- bind:value={invoice.memo}
- autocapitalize="none"
- />
- <div class="text-secondary">{invoice.memoPrompt}</div>
- </div>
-
- <button
- type="submit"
- bind:this={submit}
- class="bg-black text-white border rounded-2xl px-6 py-5 w-full font-bold"
- >
- {$t("payments.next")}
- </button>
-</form>
diff --git a/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte
deleted file mode 100644
index 700ac972..00000000
--- a/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte
+++ /dev/null
@@ -1,85 +0,0 @@
-<script>
- import { scale } from "svelte/transition";
- import Icon from "$comp/Icon.svelte";
- import { toast } from "@zerodevx/svelte-toast";
- import { browser } from "$app/environment";
- import { t } from "$lib/translations";
- import { fiat, f, s, sat, sats } from "$lib/utils";
-
- export let data;
- let amount, currency, rate, received, pending, tip, user, id;
- $: update(data);
- let update = (data) => {
- ({ amount, currency, rate, received, pending, tip, user, id } =
- data.invoice);
- };
-
- toast.pop(0);
-</script>
-
-<div class="container px-4 text-center mx-auto">
- {#if received}
- <div
- class="flex w-full py-5 max-w-[200px] mx-auto"
- in:scale={{ start: 0.5 }}
- >
- <Icon icon="check" style="mx-auto" />
- </div>
-
- <h1 class="text-3xl md:text-4xl font-bold mb-6">
- {$t("invoice.paymentSuccessful")}
- </h1>
-
- <h2 class="text-2xl md:text-3xl font-semibold">
- {f(fiat(received - tip, rate), currency)}
- {#if tip}
- <span class="text-lg">
- + {f(fiat(tip, rate), currency)}
- </span>
- {/if}
- </h2>
- <h3 class="text-secondary md:text-lg mb-6 mt-1">
- ⚡️{s(received - tip)}
-
- {#if tip}
- <span class="text-lg">
- + ⚡️{s(tip)}
- </span>
- {/if}
- </h3>
- {/if}
-
- {#if pending}
- <div
- class="flex w-full py-5 max-w-[200px] mx-auto"
- in:scale={{ start: 0.5 }}
- >
- <Icon icon="orangeclock" style="mx-auto" />
- </div>
-
- <h1 class="text-3xl md:text-4xl font-bold mb-6">Payment detected</h1>
- <h2 class="text-2xl md:text-3xl font-semibold">
- {f(fiat(pending - tip, rate), currency)}
- {#if tip}
- + {f(fiat(tip, rate), currency)}
- {/if}
- </h2>
-
- <h3 class="text-secondary md:text-lg mb-6 mt-1">
- ⚡️{s(pending - tip)}
- {#if tip}
- <span class="text-lg">
- + ⚡️{s(tip)}
- </span>
- {/if}
- </h3>
- {/if}
-</div>
-
-<a href={`/${user.username}/receive`}>
- <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" />
-</a>
-
-<div class="fixed bottom-10 w-full text-center">
- {$t("payments.tapAnywhere")}
-</div>
diff --git a/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.ts b/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.ts
deleted file mode 100644
index b81e2364..00000000
--- a/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import invoice from "$lib/invoice";
-import { redirect } from "@sveltejs/kit";
-
-export async function load({ parent, params }) {
- let { invoice } = await parent();
- if (!invoice.amount)
- redirect(307, `/${params.username}/invoice/${invoice.id}`);
-}
-
-export const actions = {
- default: invoice,
-};
diff --git a/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte
deleted file mode 100644
index 35b24bf1..00000000
--- a/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte
+++ /dev/null
@@ -1,209 +0,0 @@
-<script>
- import { onMount } from "svelte";
- import { browser } from "$app/environment";
- import { invoice as inv, request } from "$lib/store";
- import { copy, focus, f, sat, get, reverseFormat, s, sats } from "$lib/utils";
- import Icon from "$comp/Icon.svelte";
- import Slider from "$comp/Slider.svelte";
- import { goto } from "$app/navigation";
- import { t } from "$lib/translations";
- import { enhance } from "$app/forms";
- import { tick } from "svelte";
-
- const tipAmounts = ["No", "10%", "15%", "20%"];
- let showCustomAmount;
-
- let customInput;
- let customTipAmount;
-
- let apply = async () => {
- if (customTipAmount > 0) {
- tipPercent = (customTipAmount / amountFiat) * 100;
- showCustomAmount = false;
- }
- };
-
- let active = (amount) =>
- !customTipAmount && Math.round(tipPercent) === parseInt(amount.slice(0, 2));
-
- const handleCustomTipAmount = (e) => {
- tipPercent = 0;
- customTipAmount = e.target.value;
- };
-
- let timeout;
- const handleSlide = (e) => {
- customTipAmount = "";
- if (customInput) {
- customInput.value = "";
- }
- tipPercent = e.target.value;
- };
-
- let submitting;
- const handleTipButtonClick = async (amount) => {
- submitting = true;
- if (amount === "No") {
- tipPercent = 0;
- } else {
- tipPercent = parseInt(amount.slice(0, 2));
- }
-
- await tick();
- submit.click();
- };
-
- let submit;
-
- export let data;
- $: refresh(data);
- let { invoice, id, user, rates } = data;
- let {
- amount,
- hash,
- items,
- type,
- rate,
- received,
- prompt,
- text,
- tip,
- user: { username, currency },
- } = invoice;
-
- let qr;
- let tipPercent = 0;
-
- let fullscreen;
-
- let refresh = (data) => {
- $inv = null;
-
- ({ invoice, id } = data);
- ({
- amount,
- hash,
- items,
- type,
- received,
- prompt,
- text,
- tip,
- user: { username },
- } = invoice);
-
- tipPercent = (tip / amount) * 100;
- };
-
- $: currency = user?.currency || invoice.currency;
- $: rate = invoice.rate * (rates[currency] / rates[invoice.currency]);
-
- $: tip = Math.round((amount / 100) * tipPercent);
-
- $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2));
-
- $: tipAmount = ((tip * rate) / sats).toFixed(2);
-
- $: invoiceAmountFiatFormatted = f(amountFiat, currency);
-
- onMount(() => ($request = undefined));
-</script>
-
-<div class="container px-4 max-w-lg text-center mx-auto">
- <form method="POST" use:enhance class:invisible={submitting}>
- <input type="hidden" name="amount" value={amount} />
- <input type="hidden" name="tip" value={tip} />
- <input type="hidden" name="username" value={username} />
- <input type="hidden" name="rate" value={invoice.rate} />
- <input type="hidden" name="prompt" value="false" />
- <input type="hidden" name="type" value={type} />
- <input type="hidden" name="hash" value={hash} />
- <input type="hidden" name="items" value={JSON.stringify(items)} />
-
- {#if invoice.memoPrompt}
- <input type="hidden" name="memoPrompt" value={invoice.memoPrompt} />
- {/if}
-
- <h1 class="text-4xl font-semibold my-8">{$t("invoice.addTipq")}</h1>
- {#if !showCustomAmount}
- <div class="my-2 text-xl">{Math.round(tipPercent)}%</div>
-
- <Slider handle={handleSlide} bind:value={tipPercent} />
-
- <div class="flex mb-8 text-lg">
- <div>
- <span class="mr-1">
- {f(amountFiat, currency)}
- <span class="font-semibold">+{f(tipAmount, currency)}</span>
- </span>
- </div>
-
- <div class="ml-auto text-lg">
- {sat(amount)}
- <span class="font-semibold">+{sat(tip)}</span>
- </div>
- </div>
-
- {#if tip}
- <div>
- <button type="button" on:click={() => submit.click()}>Next</button>
- <button type="button" on:click={() => (tipPercent = 0)}>Reset</button>
- </div>
- {:else}
- {#each tipAmounts as amount}
- <button
- type="button"
- class:active={active(amount, tipPercent)}
- on:click={() => handleTipButtonClick(amount)}>{amount}</button
- >
- {/each}
- {/if}
-
- <div>
- <button type="button" on:click={() => (showCustomAmount = true)}
- >Custom</button
- >
- </div>
- {/if}
-
- <button type="submit" bind:this={submit} />
- </form>
-
- {#if showCustomAmount}
- <form on:submit|preventDefault={apply}>
- <div class="relative">
- <span
- class="absolute top-[18px] left-5 font-semibold {!customTipAmount
- ? 'opacity-50'
- : 'opacity-100'}">$</span
- >
- <input
- bind:this={customInput}
- type="number"
- step="0.01"
- on:input={(e) => handleCustomTipAmount(e)}
- class="pl-10"
- placeholder="Custom amount"
- use:focus
- />
- <button
- type="submit"
- class="w-full mt-2 bg-black text-white font-semibold py-3 px-7 rounded-2xl {!customTipAmount
- ? 'opacity-50'
- : 'opacity-100 hover:opacity-80'}"
- disabled={!customTipAmount}>Apply</button
- >
- </div>
- </form>
- {/if}
-</div>
-
-<style>
- .active {
- @apply !bg-black !text-white;
- }
-
- .container button[type="button"] {
- @apply w-full mt-2 bg-primary text-black hover:bg-black hover:text-white font-semibold py-5 px-7 rounded-2xl;
- }
-</style>
diff --git a/src/routes/(app)/[username]/invoice/sent/+page.svelte b/src/routes/(app)/[username]/invoice/sent/+page.svelte
deleted file mode 100644
index b95bc85e..00000000
--- a/src/routes/(app)/[username]/invoice/sent/+page.svelte
+++ /dev/null
@@ -1,11 +0,0 @@
-<script>
- import Avatar from "$comp/Avatar.svelte";
- import Icon from "$comp/Icon.svelte";
- import { request } from "$lib/store";
-</script>
-
-Invoice sent to
-<div class="flex">
- <Avatar user={$request.requester} size={12} />
- <h1 class="my-auto"><b>{$request.requester.username}</b> is ready to pay</h1>
-</div>
diff --git a/src/routes/(app)/[username]/items/[id]/+page.server.ts b/src/routes/(app)/[username]/items/[id]/+page.server.ts
deleted file mode 100644
index 67cebd93..00000000
--- a/src/routes/(app)/[username]/items/[id]/+page.server.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { fd, auth, get, post } from "$lib/utils";
-import { fail, redirect } from "@sveltejs/kit";
-
-export let load = async ({ params: { id } }) => {
- let item = await get(`/items/${id}`);
- return { item };
-};
-
-export const actions = {
- default: async ({ cookies, request, url }) => {
- let form = await fd(request);
- try {
- await post(`/items`, form, auth(cookies));
- } catch (e: any) {
- return fail(400, { message: e.message });
- }
-
- let success = `/${url.pathname.split("/")[1]}`;
- redirect(307, success);
- },
-};
diff --git a/src/routes/(app)/[username]/items/[id]/+page.svelte b/src/routes/(app)/[username]/items/[id]/+page.svelte
deleted file mode 100644
index 65720350..00000000
--- a/src/routes/(app)/[username]/items/[id]/+page.svelte
+++ /dev/null
@@ -1,18 +0,0 @@
-<script>
- import { t } from "$lib/translations";
- import ItemForm from "$comp/ItemForm.svelte";
- import { fail } from "$lib/utils";
-
- export let data;
- export let form;
-
- $: form?.message && fail(form.message);
- let { item, user } = data;
-</script>
-
-<div class="container max-w-xl mx-auto px-4">
- <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10">
- {$t("items.edit")}
- </h1>
- <ItemForm {item} {user} />
-</div>
diff --git a/src/routes/(app)/[username]/items/create/+page.server.ts b/src/routes/(app)/[username]/items/create/+page.server.ts
deleted file mode 100644
index 2e006a3e..00000000
--- a/src/routes/(app)/[username]/items/create/+page.server.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { fail, redirect } from "@sveltejs/kit";
-import { fd, auth, post } from "$lib/utils";
-
-export const actions = {
- default: async ({ cookies, request, url }) => {
- let form = await fd(request);
- try {
- await post("/items", form, auth(cookies));
- } catch (e: any) {
- return fail(400, { message: e.message });
- }
-
- let success = `/${url.pathname.split("/")[1]}`;
- redirect(307, success);
- },
-};
diff --git a/src/routes/(app)/[username]/items/create/+page.svelte b/src/routes/(app)/[username]/items/create/+page.svelte
deleted file mode 100644
index b82889c9..00000000
--- a/src/routes/(app)/[username]/items/create/+page.svelte
+++ /dev/null
@@ -1,19 +0,0 @@
-<script>
- import { t } from "$lib/translations";
- import ItemForm from "$comp/ItemForm.svelte";
- import { fail } from "$lib/utils";
-
- export let data;
- export let form;
-
- $: form?.message && fail(form.message);
- let { user } = data;
- let item = {};
-</script>
-
-<div class="container max-w-xl mx-auto px-4">
- <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10">
- {$t("items.add")}
- </h1>
- <ItemForm {item} {user} />
-</div>
diff --git a/src/routes/(app)/[username]/map/+page.server.ts b/src/routes/(app)/[username]/map/+page.server.ts
deleted file mode 100644
index 616d28f6..00000000
--- a/src/routes/(app)/[username]/map/+page.server.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { get } from "$lib/utils";
-
-// export const prerender = true;
-export let load = async () => {
- let data = { locations: [] };
-
- try {
- data = await get("/locations");
- } catch (e) {}
-
- return data;
-};
diff --git a/src/routes/(app)/[username]/map/+page.svelte b/src/routes/(app)/[username]/map/+page.svelte
deleted file mode 100644
index bfa115e4..00000000
--- a/src/routes/(app)/[username]/map/+page.svelte
+++ /dev/null
@@ -1,8 +0,0 @@
-<script>
- import LocationsMap from "$comp/LocationsMap.svelte";
- export let data;
-
- let { locations } = data;
-</script>
-
-<LocationsMap {locations} />
diff --git a/src/routes/(app)/[username]/messages/+page.server.ts b/src/routes/(app)/[username]/messages/+page.server.ts
deleted file mode 100644
index 94659621..00000000
--- a/src/routes/(app)/[username]/messages/+page.server.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { get } from "$lib/utils";
-import { redirect } from "@sveltejs/kit";
-
-interface Message {
- created_at: number;
-}
-
-interface Note {
- created_at: number;
- seen: number;
-}
-
-export async function load({ params, parent, }) {
- let { user } = await parent();
- if (user.username !== params.username) redirect(307, `/${params.username}`);
- let { since = 0 } = params;
-
- let messages = [],
- notes = [];
-
- if (user?.pubkey) {
- let { pubkey } = user;
- try {
- messages = await get(`/${pubkey}/${since}/messages`);
- messages = messages.sort((a: Message, b: Message) => b.created_at - a.created_at);
- } catch (e) {
- console.log(`failed to fetch nostr messages`, e);
- }
-
- try {
- notes = await get(`/${pubkey}/notes`);
- } catch (e) {
- console.log(`failed to fetch nostr notes for ${pubkey}`, e);
- }
-
- notes.map((e: Note) => {
- e.seen = e.created_at;
- });
- }
-
- return { messages, notes };
-}
diff --git a/src/routes/(app)/[username]/messages/+page.svelte b/src/routes/(app)/[username]/messages/+page.svelte
deleted file mode 100644
index 4738ea2c..00000000
--- a/src/routes/(app)/[username]/messages/+page.svelte
+++ /dev/null
@@ -1,92 +0,0 @@
-<script>
- import { t } from "$lib/translations";
- import Avatar from "$comp/Avatar.svelte";
- import { event as e, password } from "$lib/store";
- import { browser } from "$app/environment";
- import { decrypt } from "$lib/nostr";
-
- export let data;
-
- let { messages, notes, invoices, sent, received, subject, user, rates } =
- data;
- $: refresh(data);
- let refresh = (d) =>
- ({ messages, notes, invoices, sent, received, subject, user, rates } = d);
-
- let keys = new Set();
- let latest = [];
- let ready;
-
- e.subscribe(async (event) => {
- if (!(browser && event && ready)) return;
- if (
- event.recipient?.id === user.id &&
- !~latest.findIndex((m) => m.id === event.id)
- ) {
- event.content = await decrypt({ event, user });
-
- let i = latest.findIndex((m) => m.pubkey === event.pubkey);
- let popped;
- if (~i) popped = latest.splice(i, 1);
- else popped = latest.pop();
-
- latest.unshift(event);
- latest = latest;
- }
- });
-
- $: initialize($password);
- let initialize = async (p) => {
- let i = 0;
- ready = false;
- if (!(browser && user)) return;
- while (i < messages.length) {
- let event = messages[i];
- i++;
-
- if (!(event.author && event.recipient)) continue;
-
- let k =
- event.author.id === user.id
- ? event.recipient?.pubkey
- : event.author.pubkey;
-
- if (!keys.has(k)) {
- keys.add(k);
- event.content = await decrypt({ event, user });
- if (event.content) latest.push(event);
- }
- }
-
- latest = latest;
- ready = true;
- };
-</script>
-
-<div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5">
- <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10">
- {$t("user.messages")}
- </h1>
- {#if latest.length && user?.id === subject.id}
- <div class="relative">
- {#each latest as { content, pubkey, author, recipient }}
- {@const user = author.id === user.id ? recipient : author}
- <a href={`/${subject.username}/messages/${user.username}`}>
- <div class="flex hover:bg-gray-100 p-4 rounded-2xl">
- <div class="my-auto">
- <Avatar {user} size={"20"} disabled={true} />
- </div>
- <div class="my-auto truncate">
- <div class="my-auto ml-1 text-lg font-bold">{user.username}</div>
- <div class="my-auto ml-1 text-secondary text-lg truncate">
- {content}
- </div>
- </div>
- </div>
- </a>
- {/each}
- </div>
- {:else}
- <div class="text-center">{$t("user.noMessages")}</div>
- {/if}
-</div>
diff --git a/src/routes/(app)/[username]/messages/[recipient]/+page.server.ts b/src/routes/(app)/[username]/messages/[recipient]/+page.server.ts
deleted file mode 100644
index 6be9f6ec..00000000
--- a/src/routes/(app)/[username]/messages/[recipient]/+page.server.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { get } from "$lib/utils";
-import type { Message } from "$lib/types";
-
-export async function load({ params, parent }) {
- let { user } = await parent();
- let { since = 0 } = params;
-
- let messages: Message[] = [];
- let recipient = await get(`/users/${params.recipient}`);
-
- try {
- messages = await get(`/${user.pubkey}/${since}/messages`);
-
- messages = messages
- .filter(
- (m: Message) => m.recipient?.id === recipient.id || m.author?.id === recipient.id
- )
- .sort((a, b) => a.created_at - b.created_at);
- } catch (e) {
- console.log(`failed to fetch nostr messages`, e);
- }
-
- return { messages, recipient, user };
-}
diff --git a/src/routes/(app)/[username]/messages/[recipient]/+page.svelte b/src/routes/(app)/[username]/messages/[recipient]/+page.svelte
deleted file mode 100644
index 98638389..00000000
--- a/src/routes/(app)/[username]/messages/[recipient]/+page.svelte
+++ /dev/null
@@ -1,191 +0,0 @@
-<script>
- import { cubicInOut } from "svelte/easing";
- import { browser } from "$app/environment";
- import { t } from "$lib/translations";
- import { format } from "date-fns";
- import { scale, fade, fly } from "svelte/transition";
- import { enhance } from "$app/forms";
- import Avatar from "$comp/Avatar.svelte";
- import Icon from "$comp/Icon.svelte";
- import { back, fail, focus } from "$lib/utils";
- import { sign, send, encrypt, decrypt } from "$lib/nostr";
- import { event as e, password } from "$lib/store";
- import { tick, onMount } from "svelte";
- import { getEventHash } from "nostr-tools";
-
- export let data;
-
- function fadeScale(
- node,
- { delay = 0, duration = 200, easing = (x) => x, baseScale = 0 }
- ) {
- const o = +getComputedStyle(node).opacity;
- const m = getComputedStyle(node).transform.match(/scale\(([0-9.]+)\)/);
- const s = m ? m[1] : 1;
- const is = 1 - baseScale;
-
- return {
- delay,
- duration,
- css: (t) => {
- const eased = easing(t);
- return `opacity: ${eased * o}; transform: scale(${
- eased * s * is + baseScale
- })`;
- },
- };
- }
-
- let { messages, recipient, user } = data;
- let input, pane;
-
- $: initialize($password);
- let initialize = async (p) => {
- await Promise.all(
- messages.map(
- async (event) => (event.message = await decrypt({ event, user }))
- )
- );
-
- messages = messages;
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
- };
-
- e.subscribe(async (event) => {
- let found = ~messages.findIndex((m) => m.id === event?.id);
- if (event?.recipient.id === user.id && !found) {
- event.message = await decrypt({ event, user });
- messages.push(event);
- messages = messages;
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
- }
- });
-
- let sent, submitting, message;
- let submit = async () => {
- submitting = true;
-
- let event = {
- pubkey: user.pubkey,
- created_at: Math.floor(Date.now() / 1000),
- kind: 4,
- tags: [["p", recipient.pubkey]],
- };
-
- event.message = message;
-
- message = "";
-
- event.author = user;
- event.recipient = recipient;
-
- messages.push(event);
- messages = messages;
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
-
- try {
- event.content = await encrypt({
- message: event.message,
- recipient: recipient.pubkey,
- user,
- });
- event.id = getEventHash(event);
- await sign({ event, user });
- await send(event);
-
- sent = true;
- } catch (e) {
- console.log(e);
- fail("Failed to send message");
- }
-
- message = "";
- submitting = false;
- };
-
- let keydown = (e) => {
- if (e.key === "Enter") {
- e.preventDefault();
- submit();
- }
- };
-</script>
-
-<div class="container max-w-xl mx-auto px-4 space-y-5">
- <button
- type="button"
- class="hover:opacity-80"
- data-sveltekit-preload-data="false"
- on:click={back}
- >
- <Icon icon="arrow-left" style="w-10" />
- </button>
-
- <div
- class="h-[50vh] max-h-[50vh] overflow-y-scroll scrollbar-thin scrollbar-thumb-[#F2F6FC] scrollbar-track-white pr-8"
- bind:this={pane}
- >
- {#each messages as { id, author, message, created_at, pubkey }}
- {@const ours = pubkey === user.pubkey}
- {@const theirs = !ours}
-
- {#if message}
- <div
- class="flex gap-2"
- class:flex-row-reverse={theirs}
- class:justify-end={theirs}
- in:fadeScale={{
- easing: cubicInOut,
- baseScale: 0.5,
- }}
- >
- <div
- class="rounded-2xl px-4 py-2 max-w-[300px] mb-1 text-lg"
- class:ours
- class:theirs
- :key={id}
- >
- {message}
- </div>
- <div class="mt-auto">
- <Avatar user={author} size={"12"} />
- </div>
- </div>
- <div class="text-sm text-gray-400 mb-6" class:text-right={ours}>
- {format(new Date(created_at * 1000), "MMM d")},
- {format(new Date(created_at * 1000), "h:mm aa")}
- </div>
- {/if}
- {/each}
- </div>
-
- <form
- method="POST"
- class="w-full border rounded-xl outline-none gap-4 flex p-0 pr-2"
- on:submit|preventDefault={submit}
- >
- <input type="hidden" name="requester_id" value={user.id} />
- <input type="hidden" name="recipient" value={recipient.username} />
-
- <div
- use:focus
- contenteditable
- class="grow break-all py-4 outline-none mt-0 pl-4"
- bind:innerHTML={message}
- on:keydown={keydown}
- />
- <button type="submit" class="my-auto shrink-0">
- <Icon icon="send" style="w-8" />
- </button>
- </form>
-</div>
-
-<style>
- .ours {
- @apply bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] text-black rounded-br-none ml-auto;
- }
-
- .theirs {
- @apply bg-gray-100 text-black rounded-bl-none;
- }
-</style>
diff --git a/src/routes/(app)/[username]/payments/[...page]/+page.server.ts b/src/routes/(app)/[username]/payments/[...page]/+page.server.ts
deleted file mode 100644
index 070349f8..00000000
--- a/src/routes/(app)/[username]/payments/[...page]/+page.server.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { get, auth } from "$lib/utils";
-
-let limit = 25;
-
-export async function load({ cookies, depends, params }) {
- depends("app:payments");
-
- let parts = params.page.split("/");
-
- let start, end, page;
- if (parts.length === 1) [page] = parts;
- if (parts.length === 2) [start, page] = parts;
- if (parts.length === 3) [start, end, page] = parts;
- if (!parseInt(page)) page = 1;
-
- let offset = (page - 1) * limit;
- if (!start) start = Date.now() / 1000 - 24 * 60 * 60;
-
- let url = `/payments?start=${start * 1000}&limit=${limit}&offset=${offset}`;
- if (end) url += `&end=${end * 1000}`;
-
- let { count, payments, incoming, outgoing } = await get(url, auth(cookies));
- let pages = new Array(Math.ceil(count / limit));
-
- return { payments, page, pages, start, end, incoming, outgoing };
-}
diff --git a/src/routes/(app)/[username]/payments/[...page]/+page.svelte b/src/routes/(app)/[username]/payments/[...page]/+page.svelte
deleted file mode 100644
index 1efdc2ec..00000000
--- a/src/routes/(app)/[username]/payments/[...page]/+page.svelte
+++ /dev/null
@@ -1,315 +0,0 @@
-<script>
- import Avatar from "$comp/Avatar.svelte";
- import Icon from "$comp/Icon.svelte";
- import { onMount } from "svelte";
- import { format } from "date-fns";
- import { newPayment } from "$lib/store";
- import { t } from "$lib/translations";
- import { get, f, s, sat, sats, types } from "$lib/utils";
- import { page } from "$app/stores";
- import { differenceInDays, getUnixTime, sub } from "date-fns";
- import { goto, invalidate } from "$app/navigation";
-
- export let data;
-
- let { start, end, user, rates, payments } = data;
-
- let change = ({ target: { value } }) => goto(value);
- let link = (p) => {
- if (p.pot) return `/pot/${p.pot}`;
- if (p.with) return "/" + p.with.username;
- return `/p/${p.id}`;
- };
-
- $: presets = [
- {
- title: $t("payments.day"),
- start: sub(new Date(), { days: 1 }),
- end: null,
- },
- {
- title: $t("payments.week"),
- start: sub(new Date(), { days: 7 }),
- end: null,
- },
- {
- title: $t("payments.month"),
- start: sub(new Date(), { months: 1 }),
- end: null,
- },
- {
- title: $t("payments.all"),
- start: sub(new Date(), { years: 5 }),
- end: null,
- },
- ];
-
- $: selection = start
- ? presets.findIndex(
- (p) => Math.abs(differenceInDays(new Date(start * 1000), p.start)) < 1
- )
- : 0;
-
- let p,
- incoming,
- outgoing,
- pages = [];
- $: data &&
- ({ page: p, pages, start, end, incoming, outgoing, payments } = data);
-
- $: $page && ($newPayment = false);
- $: $newPayment && invalidate(`/users/${user.username}`);
-
- $: path = $page.params.page
- ? $page.url.pathname.substring(0, $page.url.pathname.lastIndexOf("/"))
- : $page.url.pathname;
-
- let csv = async () => {
- let url = `/${user.username}/payments`;
- if (start) url += `/${start}`;
- if (end) url += `/${end}`;
-
- let { payments } = await get(url);
-
- payments = payments.map((p) => ({
- ...p,
- created: new Date(p.created),
- total: p.amount + (p.tip || 0),
- platform_fee: p.ourfee,
- amount_fiat: f((p.amount * p.rate) / sats, p.currency),
- fee_fiat: p.fee ? f((p.fee * p.rate) / sats, p.currency) : null,
- platform_fee_fiat: p.ourfee ? f((p.ourfee * p.rate) / sats, p.currency) : null,
- tip_fiat: p.tip ? f((p.tip * p.rate) / sats, p.currency) : null,
- total_fiat: f(((p.amount + (p.tip || 0)) * p.rate) / sats, p.currency),
- }));
-
- let keys = [
- "type",
- "id",
- "created",
- "rate",
- "currency",
- "amount",
- "tip",
- "total",
- "fee",
- "platform_fee",
- "amount_fiat",
- "tip_fiat",
- "total_fiat",
- "fee_fiat",
- "platform_fee_fiat",
- ];
-
- let csv =
- keys.map((k) => `"${k}"`).join(",") +
- "\n" +
- payments
- .map((r) => keys.map((k) => `"${r[k] || ""}"`).join(","))
- .join("\n");
-
- let filename = "payments.csv";
- let blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
- if (navigator.msSaveBlob) {
- navigator.msSaveBlob(blob, filename);
- } else {
- let link = document.createElement("a");
- if (link.download !== undefined) {
- let url = URL.createObjectURL(blob);
- link.setAttribute("href", url);
- link.setAttribute("download", filename);
- link.style.visibility = "hidden";
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- }
- }
- };
-</script>
-
-<div class="mt-24 mb-20 payments">
- <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10">
- {$t("payments.header")}
- </h1>
-
- <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5">
- <div class="flex text-md text-secondary relative">
- <div class="mx-auto flex justify-center w-full gap-1">
- {#each presets as { start, end, title }, i}
- <a
- class:active={selection === i}
- href={`/${user.username}/payments/${getUnixTime(start) + "/"}1`}
- >
- <button
- class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px]"
- >
- <div class="my-auto">{title}</div>
- </button>
- </a>
- {/each}
- </div>
- </div>
-
- <div class="flex flex-wrap justify-center mb-8">
- {#if pages.length > 1}
- {#each pages as _, i}
- <a
- class="mr-1 last:mr-0"
- href={`${path}/${i + 1}`}
- class:active={parseInt(p) === i + 1}
- >
- <div
- class="border py-2 rounded-full border-2 w-12 h-12 hover:opacity-80 text-center"
- >
- {i + 1}
- </div>
- </a>
- {/each}
- {/if}
- </div>
-
- <div class="text-base">
- {#each payments as p}
- <a href={`/payment/${p.id}`}>
- <div class="grid grid-cols-12 border-b h-24 hover:bg-gray-100 px-4">
- <div class="whitespace-nowrap my-auto col-span-3">
- <div class="font-bold" class:text-red-800={p.amount < 0}>
- {f(Math.abs(p.amount) * (p.rate / sats), p.currency)}
- {#if p.tip}
- <span class="text-sm text-secondary">
- +{Math.round((p.tip / Math.abs(p.amount)) * 100)}%
- </span>
- {/if}
- </div>
-
- <div class="text-secondary">
- {sat(Math.abs(p.amount) + (p.tip || 0))}
- </div>
- </div>
-
- <div
- class="flex my-auto col-span-5 truncate text-ellipsis overflow-hidden mx-auto"
- >
- {#if p.type === types.pot}
- <a href={`/pot/${p.memo}`}>
- <div class="text-secondary flex">
- <div class="my-auto mr-1">
- <img src="/images/moneypot.png" class="w-12" alt="Pot" />
- </div>
-
- <div class="my-auto">Pot</div>
- </div>
- </a>
- {:else if p.with}
- <div class="flex">
- <div class="my-auto">
- <Avatar user={p.with} size={12} disabled={true} />
- </div>
- <div class="my-auto ml-1 text-secondary">
- {p.with.username}
- </div>
- </div>
- {:else}
- <div class="text-secondary flex">
- {#if [types.ecash, types.lightning].includes(p.type)}
- <div class="text-3xl">⚡️</div>
- {:else if p.type === types.bitcoin}
- <div class="my-auto mr-1">
- <img
- src="/images/bitcoin.svg"
- class="w-12 border-4 border-transparent"
- alt="Bitcoin"
- />
- </div>
- {:else if p.type === types.liquid}
- <div class="my-auto mr-1">
- <img
- src="/images/liquid.svg"
- class="w-12 border-4 border-transparent"
- alt="Liquid"
- />
- </div>
- {/if}
-
- <div class="my-auto">
- {p.amount > 0
- ? p.confirmed
- ? "Received"
- : "Pending"
- : "Sent"}
- </div>
- </div>
- {/if}
- </div>
-
- <div class="text-secondary text-right text-sm my-auto col-span-3">
- <div>
- {format(new Date(p.created), "h:mm aaa")}
- </div>
- <div>
- {format(new Date(p.created), "MMM d, yy")}
- </div>
- </div>
- </div>
- </a>
- {:else}
- <p class="text-secondary text-lg text-center">{$t("payments.empty")}</p>
- {/each}
- </div>
-
- <div class="grid grid-cols-3 w-full text-center text-lg">
- {#each Object.keys(incoming) as c}
- <span class="text-base text-secondary text-left"></span>
- <!-- <span class="text-base text-secondary" -->
- <!-- >{$t("payments.subtotal")}</span -->
- <!-- > -->
- <span class="text-base text-secondary">
- {#if tipsIn || tipsOut}{$t("payments.tips")}{/if}
- </span>
- <span class="text-base text-secondary"
- >{$t("payments.total")}</span
- >
-
- {@const totalIn = incoming[c]?.fiat || 0}
- {@const tipsIn = incoming[c]?.fiatTips || 0}
- {@const subtotalIn = totalIn - tipsIn}
-
- {@const totalOut = -outgoing[c]?.fiat || 0}
- {@const tipsOut = outgoing[c]?.fiatTips || 0}
- {@const subtotalOut = totalOut - tipsOut}
-
- <span class="text-left text-base text-secondary"
- >{$t("payments.income")}</span
- >
- <!-- <span><b>{subtotalIn ? f(subtotalIn, c) : "-"}</b></span> -->
- <span><b>{tipsIn ? f(tipsIn, c) : "-"}</b></span>
- <span><b>{totalIn ? f(totalIn, c) : "-"}</b></span>
-
- <span class="text-left text-base text-secondary"
- >{$t("payments.expenditure")}</span
- >
- <!-- <span><b>{subtotalOut ? f(subtotalOut, c) : "-"}</b></span> -->
- <span><b>{tipsOut ? f(tipsOut, c) : "-"}</b></span>
- <span><b>{totalOut ? f(totalOut, c) : "-"}</b></span>
- {/each}
- </div>
-
- <button
- class="ml-auto rounded-full border py-2 px-4 w-36 hover:opacity-80 flex mx-auto"
- on:click={csv}
- >
- <Icon icon="save" style="opacity-50 mr-2 my-auto" />
- <div class="my-auto">{$t("payments.export")}</div>
- </button>
- </div>
-</div>
-
-<style>
- .active * {
- @apply bg-black text-white border-black;
- }
-
- .payments {
- view-transition-name: payments;
- }
-</style>
diff --git a/src/routes/(app)/[username]/payments/[...page]/+server.ts b/src/routes/(app)/[username]/payments/[...page]/+server.ts
deleted file mode 100644
index a81faaf3..00000000
--- a/src/routes/(app)/[username]/payments/[...page]/+server.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { get, auth } from "$lib/utils";
-import { json } from "@sveltejs/kit";
-
-export async function GET({ cookies, params }) {
- let parts = params.page.split("/");
-
- let start, end;
- if (parts.length === 1) [start] = parts;
- if (parts.length === 2) [start, end] = parts;
-
- if (!start) start = Date.now() / 1000 - 24 * 60 * 60;
-
- let url = `/payments?start=${start * 1000}`;
- if (end) url += `&end=${end * 1000}`;
-
- let { payments } = await get(url, auth(cookies));
- return json({ payments });
-}
diff --git a/src/routes/(app)/[username]/invoice/[id]/+page.svelte b/src/routes/(app)/[username]/receive/+page.svelte
index 28bfe27b..11587211 100644
--- a/src/routes/(app)/[username]/invoice/[id]/+page.svelte
+++ b/src/routes/(app)/[username]/receive/+page.svelte
@@ -1,5 +1,4 @@
<script>
- import { enhance } from "$app/forms";
import { send } from "$lib/socket";
import {
btc,
@@ -28,7 +27,6 @@
export let data;
- let ndef;
let showOptions;
let reading = async ({ message, serialNumber }) => {
@@ -41,6 +39,7 @@
$: refresh(data);
let { invoice, id, subject, user, src } = data;
let {
+ aid,
amount,
hash,
type,
@@ -56,6 +55,7 @@
let refresh = async (data) => {
({ invoice, id, user, src } = data);
({
+ aid,
amount,
hash,
type,
@@ -66,21 +66,6 @@
} = invoice);
tipPercent = (tip / amount) * 100;
-
- // if (browser && window.NDEFReader) {
- // try {
- // ndef = new NDEFReader();
- // await ndef.scan();
- //
- // ndef.removeEventListener('readingerror', readingerror);
- // ndef.removeEventListener('reading', reading);
- //
- // ndef.addEventListener('readingerror', readingerror);
- // ndef.addEventListener('reading', reading);
- // } catch (e) {
- // console.log(e);
- // }
- // }
};
$: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2));
@@ -96,14 +81,7 @@
send("subscribe", invoice);
});
- if ($amountPrompt && !tip) toggleAmount();
- }
- });
-
- onDestroy(() => {
- if (ndef) {
- ndef.removeEventListener("readingerror", readingerror);
- ndef.removeEventListener("reading", reading);
+ if ($amountPrompt && tip === undefined) toggleAmount();
}
});
@@ -114,7 +92,7 @@
let setType = async (type) => {
invoice.type = type;
- ({ id } = await post(`/${username}/invoice`, {
+ ({ id } = await post(`/invoice`, {
invoice,
user: { username, currency },
}));
@@ -128,7 +106,7 @@
amount = newAmount;
invoice.amount = newAmount;
invoice.tip = 0;
- ({ id } = await post(`/${username}/invoice`, {
+ ({ id } = await post(`/invoice`, {
invoice,
user: { username, currency },
}));
@@ -157,14 +135,14 @@
>
<img
{src}
- class="mx-auto z-10 shadow-inner rounded-2xl"
+ class="mx-auto z-10 max-w-[360px]"
bind:this={qr}
alt={txt}
/>
</a>
</div>
{:else}
- <div class="break-all pb-5 text-lg text-center">
+ <div class="break-all pb-5 text-center text-secondary text-xl">
{txt}
</div>
{/if}
@@ -209,7 +187,7 @@
</div>
{/if}
- <div class="text-secondary space-y-2 text-xl">
+ <div class="text-secondary space-y-2 text-xl pt-2">
<button
type="button"
class="flex gap-2 text-center break-all rounded-2xl hover:opacity-80 py-5 px-6 w-full mx-auto justify-center border whitespace-nowrap mb-2"
@@ -257,72 +235,72 @@
</div>
</div>
- <div class="flex justify-around text-secondary">
- <button
- class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 w-full justify-center"
- class:bg-primary={type === types.bitcoin}
- class:text-black={type === types.bitcoin}
- on:click={() => setType(types.bitcoin)}
- >
- <img src="/images/bitcoin.svg" class="w-8" alt="Bitcoin" />
- <div class="my-auto text-lg">Bitcoin</div>
- </button>
+ {#if aid === user.id}
+ <div class="grid grid-cols-2 justify-around text-secondary">
+ <button
+ class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center"
+ class:bg-primary={type === types.bitcoin}
+ class:text-black={type === types.bitcoin}
+ on:click={() => setType(types.bitcoin)}
+ >
+ <img src="/images/bitcoin.svg" class="w-8" alt="Bitcoin" />
+ <div class="my-auto text-lg">Bitcoin</div>
+ </button>
- <button
- class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 w-full justify-center"
- class:bg-primary={type === types.liquid}
- class:text-black={type === types.liquid}
- on:click={() => setType(types.liquid)}
- >
- <img src="/images/liquid.svg" class="w-8" alt="Liquid" />
- <div class="my-auto text-lg">Liquid</div>
- </button>
+ <button
+ class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center"
+ class:bg-primary={type === types.liquid}
+ class:text-black={type === types.liquid}
+ on:click={() => setType(types.liquid)}
+ >
+ <img src="/images/liquid.svg" class="w-8" alt="Liquid" />
+ <div class="my-auto text-lg">Liquid</div>
+ </button>
- <button
- class="hover:bg-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 w-full justify-center"
- class:bg-primary={type === types.lightning}
- class:text-black={type === types.lightning}
- on:click={() => setType(types.lightning)}
- >
- <div class="bg-white rounded-full w-8 h-8 text-center flex">
- <div class="m-auto">⚡️</div>
- </div>
- <div class="my-auto text-lg">Lightning</div>
- </button>
- </div>
+ <button
+ class="hover:bg-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center"
+ class:bg-primary={type === types.lightning}
+ class:text-black={type === types.lightning}
+ on:click={() => setType(types.lightning)}
+ >
+ <div class="bg-white rounded-full w-8 h-8 text-center flex">
+ <div class="m-auto">⚡️</div>
+ </div>
+ <div class="my-auto text-lg">Lightning</div>
+ </button>
- {#if type === types.liquid}
- <div class="flex justify-center">
- <div class="my-auto text-xl text-center text-secondary">
- {$t("payments.onlyLbtc")}
- <span class="text-teal-500 font-bold">L-BTC</span>,
- {$t("payments.dontDeposit")}
- </div>
+ {#if user?.id === invoice?.user?.id}
+ <a href={`/receive`} class="block contents">
+ <button
+ class="hover:bg-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center"
+ >
+ <img src="/images/cash.png" class="w-8 my-auto" />
+ <div class="my-auto text-lg">Ecash</div>
+ </button>
+ </a>
+ {/if}
</div>
- {/if}
- {#if user?.id === invoice?.user?.id}
- <a href={`/receive`} class="block">
- <button
- class="flex gap-2 text-center break-all rounded-2xl hover:opacity-80 py-5 px-6 w-full mx-auto justify-center border text-xl whitespace-nowrap text-secondary"
- >
- <Icon icon="voucher" style="w-8 my-auto" />
- <div class="my-auto">
- {$t("payments.redeemVoucher")}
- </div></button
- >
- </a>
+ {#if type === types.liquid}
+ <div class="flex justify-center">
+ <div class="my-auto text-xl text-center text-secondary">
+ {$t("payments.onlyLbtc")}
+ <span class="text-teal-500 font-bold">L-BTC</span>,
+ {$t("payments.dontDeposit")}
+ </div>
+ </div>
+ {/if}
{/if}
</div>
{#if settingAmount}
<div
- class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-20"
+ class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
>
<div
- class="relative top-1/3 mx-auto p-12 border w-96 shadow-lg rounded-md bg-white space-y-5"
+ class="relative mx-auto p-12 border w-96 shadow-lg rounded-md bg-white space-y-5 top-10"
>
- <form submit={setAmount}>
+ <form on:submit|preventDefault={setAmount}>
<Numpad
bind:amount={newAmount}
bind:currency
@@ -332,6 +310,7 @@
/>
<div class="w-full flex flex-wrap gap-2">
<button
+ bind:this={submit}
type="submit"
on:click={setAmount}
class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto bg-black text-white w-full"
@@ -339,7 +318,6 @@
<div class="my-auto">Ok</div>
</button>
<button
- bind:this={submit}
type="button"
class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto w-full"
on:click={toggleAmount}
@@ -350,8 +328,8 @@
</div>
</form>
- <div class="flex justify-between items-center">
- <span class="font-bold">{$t("payments.amountPrompt")}</span>
+ <div class="flex justify-center gap-3">
+ <div class="text-secondary">{$t("payments.amountPrompt")}</div>
<Toggle id="notify" bind:value={$amountPrompt} />
</div>
</div>
diff --git a/src/routes/(app)/[username]/receive/[...param]/+page.server.ts b/src/routes/(app)/[username]/receive/[...param]/+page.server.ts
deleted file mode 100644
index 502678a9..00000000
--- a/src/routes/(app)/[username]/receive/[...param]/+page.server.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { redirect } from "@sveltejs/kit";
-import { auth, post } from "$lib/utils";
-import type { Invoice } from "$lib/types";
-
-export let load = async ({ cookies, parent }) => {
- let {subject, user, rates } = await parent();
-
- let invoice: Invoice = {
- type: "lightning",
- rate: rates[user?.currency || subject?.currency],
- };
-
- if(!user) user = subject;
- invoice = await post("/invoice", { invoice, user }, auth(cookies));
- let { id } = invoice;
-
- if (invoice.memoPrompt && !invoice.memo) {
- redirect(307, `/${user.username}/invoice/${id}/memo`);
- } else redirect(307, `/${user.username}/invoice/${id}`);
-};
diff --git a/src/routes/(app)/[username]/receive/[...param]/+page.svelte b/src/routes/(app)/[username]/receive/[...param]/+page.svelte
deleted file mode 100644
index f9267105..00000000
--- a/src/routes/(app)/[username]/receive/[...param]/+page.svelte
+++ /dev/null
@@ -1,116 +0,0 @@
-<script>
- import { page } from "$app/stores";
- import { fly } from "svelte/transition";
- import { enhance } from "$app/forms";
- import { tick } from "svelte";
- import { browser } from "$app/environment";
- import { t } from "$lib/translations";
- import AppHeader from "$comp/AppHeader.svelte";
- import Avatar from "$comp/Avatar.svelte";
- import Icon from "$comp/Icon.svelte";
- import Spinner from "$comp/Spinner.svelte";
- import { back, fail, focus } from "$lib/utils";
-
- export let data;
- export let form;
- data.subject = data.user;
-
- let { contacts } = data;
- let w;
-
- let el, text;
-
- let keypress = (e) => e.key === "Enter" && (e.preventDefault() || el.click());
-
- let paste = async () => {
- text = await navigator.clipboard.readText();
- };
-</script>
-
-<svelte:window bind:innerWidth={w} />
-
-<AppHeader {data} />
-<div class="container px-4 max-w-lg mx-auto space-y-5 mt-20">
- <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold">
- {$t("payments.send")}
- </h1>
-
- <form method="POST" use:enhance class="space-y-2 text-xl">
- {#if form?.error}
- <div class="mb-5">
- <div class="text-red-600">
- {$t("error.unrecognizedInput")}
- </div>
- </div>
- {/if}
-
- <div class="mb-2">
- <textarea
- use:focus
- name="text"
- placeholder={$t("user.send.placeholder")}
- on:keypress={keypress}
- class="w-full p-4 border rounded-xl h-48 text-xl"
- bind:value={text}
- autocapitalize="none"
- />
- </div>
-
- <div class="flex gap-2">
- <a href="/scan" class="w-full">
- <button
- type="button"
- class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center"
- >
- <Icon icon="camera" style="mr-2 w-6 my-auto" />
- <div class="my-auto">{$t("user.send.scan")}</div>
- </button>
- </a>
-
- <button
- type="button"
- class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center"
- on:click={paste}
- >
- <Icon icon="paste" style="mr-2 w-6 my-auto" />
- <div class="my-auto">{$t("user.send.paste")}</div>
- </button>
- </div>
-
- <button
- bind:this={el}
- type="submit"
- class="bg-black text-white border rounded-2xl px-6 py-5 w-full font-bold"
- >
- {$t("user.send.next")}
- </button>
- </form>
-
- {#if contacts.length}
- <div class="space-y-5">
- <h1 class="px-3 md:px-0 text-2xl font-semibold mt-10">
- {$t("user.send.contacts")}
- </h1>
- <div>
- {#each contacts as c}
- <a href={`/pay/${c.username}`}>
- <div
- class="border-b p-2 last:border-b-0 hover:bg-gray-100 rounded-2xl"
- >
- <div class="flex">
- <div>
- <div class="flex">
- <Avatar user={c} size={20} disabled={true} />
- <div class="my-auto text-left">
- <p class="ml-1 text-lg break-words">{c.username}</p>
- </div>
- </div>
- </div>
- </div>
- </div>
- </a>
- {/each}
- </div>
- </div>
- {/if}
-</div>
diff --git a/src/routes/(app)/[username]/reset/+server.ts b/src/routes/(app)/[username]/reset/+server.ts
deleted file mode 100644
index f7dba02c..00000000
--- a/src/routes/(app)/[username]/reset/+server.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { json } from "@sveltejs/kit";
-import { auth, post } from "$lib/utils";
-
-export async function POST({ cookies, request, params }) {
- let { username } = params;
- let r = await request.json();
- let { password } = r;
- let result = await post("/reset", { password, username }, auth(cookies));
- return json(result);
-}
diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.server.ts b/src/routes/(app)/[username]/settings/[...tab]/+page.server.ts
deleted file mode 100644
index 42639b38..00000000
--- a/src/routes/(app)/[username]/settings/[...tab]/+page.server.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { fail, redirect } from "@sveltejs/kit";
-import { fd, auth, get, post } from "$lib/utils";
-
-export function load({ cookies, params, url }) {
- if (url.pathname.endsWith("settings"))
- redirect(307, url.pathname + "/account");
- params.cookies = cookies.getAll();
- return { cookies: cookies.getAll(), tab: params.tab };
-}
-
-export const actions = {
- default: async ({ cookies, request }) => {
- let form = await fd(request);
-
- if (form.tab === "pos") {
- form.notify = form.notify === "on";
- form.nip5 = form.nip5 === "on";
- form.prompt = form.prompt === "on";
- form.autowithdraw = form.autowithdraw === "on";
- }
-
- let user = { ...(await get("/me", auth(cookies))), ...form };
-
- try {
- ({ user } = await post(`/user`, user, auth(cookies)));
- } catch (e: any) {
- return fail(400, { message: e.message });
- }
-
- if (user.language) cookies.set("lang", user.language, { path: "/" });
-
- return { user, success: true };
- },
-};
diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte
deleted file mode 100644
index 12980291..00000000
--- a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte
+++ /dev/null
@@ -1,257 +0,0 @@
-<script>
- import { browser } from "$app/environment";
- import { onMount, tick } from "svelte";
- import { fly } from "svelte/transition";
- import { applyAction, deserialize } from "$app/forms";
-
- import Icon from "$comp/Icon.svelte";
- import Spinner from "$comp/Spinner.svelte";
- import Pin from "$comp/Pin.svelte";
- import { loading, t } from "$lib/translations";
- import { fail, auth, post, sleep, warning, success } from "$lib/utils";
- import { avatar, banner, password, pin } from "$lib/store";
- import { upload } from "$lib/upload";
- import { page } from "$app/stores";
- import { sign, send, reEncryptEntropy, setNsec } from "$lib/nostr";
- import { invalidateAll } from "$app/navigation";
-
- import Account from "./_account.svelte";
- import Pos from "./_pos.svelte";
- import Security from "./_security.svelte";
- import Shopify from "./_shopify.svelte";
-
- import { PUBLIC_COINOS_URL } from "$env/static/public";
-
- export let data;
- export let form;
-
- let submit;
- let formElement;
-
- let { user, token, rates, tab, cookies } = data;
- let prev;
-
- $: update(data);
- let update = () => {
- ({ user, token, rates, tab } = data);
- prev = { ...user };
- };
-
- $: form?.user && ({ user } = form);
-
- $: form?.success && throttledSuccess();
-
- let justUpdated;
- let throttledSuccess = () => {
- if (!justUpdated) {
- justUpdated = true;
- success($t("user.settings.saved"), false);
- setTimeout(() => (justUpdated = false), 5000);
- }
- };
-
- $: form?.message && fail(form.message);
-
- $: if (form?.message?.startsWith("Pin")) {
- fail("Wrong pin, try again");
- $pin = "";
- }
-
- let tabs = [
- { name: "account", key: "ACCOUNT", comp: Account },
- { name: "pos", key: "POINT_OF_SALE", comp: Pos },
- { name: "security", key: "SECURITY", comp: Security },
- { name: "shopify", key: "SHOPIFY", comp: Shopify },
- ];
-
- $: ({ comp } = tabs.find((t) => t.name === tab));
-
- let { address, id, username } = user;
- let submitting;
-
- $: if (!$loading && $page.url.searchParams.get("verified"))
- success($t("user.settings.verified"));
-
- async function handleSubmit() {
- try {
- submitting = true;
- let data = new FormData(formElement);
-
- if (data.get("newNsec")) {
- await setNsec(user, data.get("newNsec"));
- data.set("nsec", user.nsec);
- data.set("pubkey", user.pubkey);
- }
-
- if (data.get("password")) {
- try {
- data.set(
- "cipher",
- await reEncryptEntropy(user, data.get("password"))
- );
- } catch (e) {
- console.log("Failed to encrypt keys with new password");
- throw e;
- }
- }
-
- if ($avatar) {
- try {
- let { hash } = JSON.parse(
- await upload($avatar.file, $avatar.type, $avatar.progress, token)
- );
-
- data.set("profile", hash);
-
- await fetch(`/api/public/${hash}.webp`, {
- cache: "reload",
- mode: "no-cors",
- });
- } catch (e) {
- console.log("problem upsubmitting avatar", e);
- }
- }
-
- if ($banner) {
- try {
- let { hash } = JSON.parse(
- await upload($banner.file, $banner.type, $banner.progress, token)
- );
-
- data.set("banner", hash);
-
- await fetch(`/api/public/${hash}.webp`, {
- cache: "reload",
- mode: "no-cors",
- });
- } catch (e) {
- console.log("problem upsubmitting banner", e);
- }
- }
-
- if (["username", "address", "profile"].some((a) => user[a] !== prev[a])) {
- let event = {
- pubkey: user.pubkey,
- created_at: Math.floor(Date.now() / 1000),
- kind: 0,
- content: JSON.stringify({
- name: user.username,
- about: user.address,
- picture: `${PUBLIC_COINOS_URL}/public/${user.profile}.webp`,
- }),
- tags: [],
- };
-
- try {
- await sign({ event, user });
- await send(event);
- } catch (e) {
- warning("Nostr profile could not be updated");
- }
- }
-
- if (user.email !== prev.email) {
- try {
- cookies.get = function (n) {
- return this.find((c) => c.name === n).value;
- };
-
- user.verified = false;
-
- await post(
- "/api/request",
- { id: user.id, email: user.email },
- auth(cookies)
- );
-
- warning($t("user.settings.verifying"), false);
- } catch (e) {
- fail(e.message);
- console.log(e);
- }
- }
-
- const response = await fetch(formElement.action, {
- method: "POST",
- body: data,
- });
-
- const result = deserialize(await response.text());
-
- if (result.type === "success") {
- await invalidateAll();
- if (data.get("password")) $password = data.get("password");
- }
-
- applyAction(result);
- } catch (e) {
- console.log(e);
- fail("Something went wrong");
- }
-
- submitting = false;
- }
-</script>
-
-{#if user.haspin && $pin?.length !== 6}
- <Pin />
-{/if}
-
-<form
- method="POST"
- class="mb-[154px] settings"
- on:submit|preventDefault={handleSubmit}
- bind:this={formElement}
->
- <input type="hidden" name="pin" value={$pin} />
- <input type="hidden" name="tab" value={tab} />
-
- <div class="mt-24 mb-20 px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8">
- <div class="header">
- <h1 class="text-center text-3xl md:text-4xl font-semibold mb-10">
- {$t("user.settings.header")}
- </h1>
-
- <div
- class="font-bold flex justify-around items-center border-b pb-3 text-secondary"
- >
- {#each tabs.filter((t) => t.name !== "shopify") as { name, key }}
- <a href={`/${user.username}/settings/${name}`}>
- <button
- type="button"
- class="hover:opacity-80"
- class:text-black={tab === name ||
- (tab === "shopify" && name === "pos")}
- >{$t(`user.settings.${key}`)}</button
- >
- </a>
- {/each}
- </div>
- </div>
-
- <svelte:component this={comp} {user} {rates} {submit} />
- </div>
-
- <div
- class="z-10 fixed bottom-0 bg-white shadow border-t w-full flex justify-center items-center py-3"
- >
- <button
- bind:this={submit}
- type="submit"
- class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80"
- class:bg-black={submitting}
- >
- {#if submitting}
- <Spinner />
- {:else}
- <div class="my-auto">{$t("user.settings.saveSettings")}</div>
- {/if}
- </button>
- </div>
-</form>
-
-<style>
- .settings {
- view-transition-name: settings;
- }
-</style>
diff --git a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte
deleted file mode 100644
index 8e3323c7..00000000
--- a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte
+++ /dev/null
@@ -1,190 +0,0 @@
-<script>
- import { tick } from "svelte";
- import { avatar, banner } from "$lib/store";
- import Icon from "$comp/Icon.svelte";
- import { t } from "$lib/translations";
- import { page } from "$app/stores";
-
- export let user, rates, submit;
-
- let { id } = user;
-
- let avatarFile, avatarInput, bannerFile, bannerInput;
-
- let selectAvatar = () => avatarInput.click();
- let selectBanner = () => bannerInput.click();
-
- let percent;
- let progress = async (event) => {
- percent = Math.round((event.loaded / event.total) * 100);
- };
-
- let tooLarge = {};
-
- let handleFile = async ({ target }, type) => {
- tooLarge[type] = false;
- let file = target.files[0];
- if (!file) return;
-
- if (file.size > 10000000) return (tooLarge[type] = true);
-
- if (type === "profile") {
- $avatar = { id, file, type, progress };
- } else if (type === "banner") {
- $banner = { id, file, type, progress };
- }
-
- var reader = new FileReader();
- reader.onload = async (e) => {
- if (type === "profile") {
- $avatar.src = e.target.result;
- } else if (type === "banner") {
- $banner.src = e.target.result;
- }
- };
-
- reader.readAsDataURL(file);
- };
-
- if (!user.display) user.display = user.username;
-
- $: url = `${$page.url.host}/${user.username}`;
- $: full = `${$page.url.protocol}//${url}`;
- $: addr = `${user.username}@${$page.url.host}`;
-</script>
-
-<div>
- <label for="username" class="font-bold mb-1 block"
- >{$t("user.settings.username")}</label
- >
- <div class="flex mb-2">
- <input
- type="text"
- name="username"
- bind:value={user.username}
- class="w-auto min-w-0 pr-1 grow"
- />
- </div>
-</div>
-
-<div>
- <label for="display" class="font-bold mb-1 block"
- >{$t("user.settings.displayName")}</label
- >
- <input type="text" name="display" bind:value={user.display} />
-</div>
-
-<div class="space-y-1 relative">
- <label for="email" class="font-bold block">{$t("user.settings.email")}</label>
- <p class="text-secondary">
- {$t("user.settings.emailDescription")}
- </p>
-
- <div class="relative">
- <input type="text" name="email" bind:value={user.email} />
- {#if user.verified}
- <Icon icon="check" style="absolute right-5 top-3 w-8" />
- {:else if user.email}
- <Icon icon="orangeclock" style="absolute right-5 top-3 w-8" />
- {/if}
- </div>
-</div>
-
-<div>
- <span class="font-bold">{$t("user.settings.profileImage")}</span>
-
- <div class="flex">
- {#if $avatar || user.profile}
- <div
- class="relative rounded-full overflow-hidden text-center w-20 h-20 my-auto hover:opacity-80 cursor-pointer"
- on:click={selectAvatar}
- on:keydown={selectAvatar}
- >
- <img
- src={$avatar?.src || `/api/public/${user.profile}.webp`}
- class="absolute w-full h-full object-cover object-center visible overflow-hidden"
- alt={user.username}
- />
- </div>
- {:else}
- <div
- class="rounded-full border-4 border-white p-4 bg-gradient-to-r from-primary to-gradient w-24 my-auto hover:opacity-80 cursor-pointer"
- on:click={selectAvatar}
- on:keydown={selectAvatar}
- >
- <Icon icon="logo-symbol-white" style="mx-auto" />
- </div>
- {/if}
- <div class="ml-2 p-2">
- <!-- found missing translation -->
- <button
- type="button"
- class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80"
- on:click={selectAvatar}
- on:keydown={selectAvatar}>{$t("user.settings.select")}</button
- >
- <input
- type="file"
- class="hidden"
- bind:this={avatarInput}
- on:change={(e) => handleFile(e, "profile")}
- />
- </div>
- </div>
-
- {#if tooLarge["avatar"]}
- <div class="text-red-600">Max file size 10MB</div>
- {/if}
-</div>
-
-<div>
- <div class="flex justify-between items-center">
- <span class="font-bold">{$t("user.settings.bannerImage")}</span>
- </div>
-
- {#if $banner || user.banner}
- <img
- src={$banner ? $banner.src : `/api/public/${user.banner}.webp`}
- class="w-full object-cover object-center visible overflow-hidden h-48 mb-4 hover:opacity-80"
- on:click={selectBanner}
- on:keydown={selectBanner}
- alt="Banner"
- />
- {:else}
- <div
- class="bg-gradient-to-r from-primary to-gradient w-full h-48 mb-4 cursor-pointer hover:opacity-80"
- on:click={selectBanner}
- on:keydown={selectBanner}
- alt="Banner"
- />
- {/if}
-
- <button
- type="button"
- class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80"
- on:click={selectBanner}
- on:keydown={selectBanner}>{$t("user.settings.select")}</button
- >
- <input
- type="file"
- class="hidden"
- bind:this={bannerInput}
- on:change={(e) => handleFile(e, "banner")}
- />
-
- {#if tooLarge["banner"]}
- <div class="text-red-600">Max file size 10MB</div>
- {/if}
-</div>
-
-<div>
- <label for="address" class="font-bold mb-1 block"
- >{$t("user.settings.about")}</label
- >
- <textarea
- type="text"
- name="address"
- bind:value={user.address}
- placeholder={$t("user.settings.aboutPlaceholder")}
- />
-</div>
diff --git a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte
deleted file mode 100644
index 6a1c92a1..00000000
--- a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte
+++ /dev/null
@@ -1,238 +0,0 @@
-<script>
- import { tick } from "svelte";
- import Icon from "$comp/Icon.svelte";
- import Numpad from "$comp/Numpad.svelte";
- import LocaleSelector from "$comp/LocaleSelector.svelte";
- import Toggle from "$comp/Toggle.svelte";
- import { locale, t } from "$lib/translations";
- import { success, fail } from "$lib/utils";
- import { page } from "$app/stores";
-
- export let user, rates, submit;
-
- let { currency } = user;
- let rate = rates[currency];
-
- let fiats = Object.keys(rates).sort((a,b) => a.localeCompare(b));
- $: user.language = $locale;
- let keypress = (e) => e.key === "Enter" && (e.preventDefault() || el.click());
-
- let editingReserve, editingThreshold, doneReserve, doneThreshold;
- let doneEditing = () => {
- editingReserve = false;
- editingThreshold = false;
- };
-
- let editReserve = async () => {
- editingReserve = true;
- await tick();
- reserveEl.focus();
- };
-
- let editThreshold = async () => {
- editingThreshold = true;
- await tick();
- thresholdEl.focus();
- };
-
- if (!user.threshold) user.threshold = 1000000;
- if (!user.reserve) user.reserve = 100000;
- let reserveEl, thresholdEl;
-</script>
-
-<div>
- <label for="language" class="font-bold block mb-1"
- >{$t("user.settings.locale")}</label
- >
- <LocaleSelector style="select-styles block py-3 w-full" />
-</div>
-
-<div>
- <label for="currency" class="font-bold block mb-1"
- >{$t("user.settings.localCurrency")}</label
- >
- <select
- name="currency"
- class="select-styles block py-3 w-full"
- value={user.currency}
- >
- {#each fiats as fiat}
- <option value={fiat}>{fiat}</option>
- {/each}
- </select>
-</div>
-
-<div>
- <label for="memoPrompt" class="font-bold mb-1 block"
- >{$t("user.settings.memoPrompt")}</label
- >
- <textarea
- name="memoPrompt"
- placeholder={$t("user.settings.memoPromptPlaceholder")}
- bind:value={user.memoPrompt}
- />
-</div>
-
-<div>
- <div class="flex justify-between items-center">
- <span class="font-bold">{$t("user.settings.notifications")}</span>
- <Toggle id="notify" bind:value={user.notify} />
- </div>
- <p class="text-secondary mt-1 w-9/12">
- {$t("user.settings.notificationsDesc")}
- </p>
-</div>
-
-<div>
- <div class="flex justify-between items-center">
- <span class="font-bold">{$t("user.settings.tipPrompt")}</span>
- <Toggle id="prompt" bind:value={user.prompt} />
- </div>
- <p class="text-secondary mt-1 w-9/12">
- {$t("user.settings.tipPromptDescription")}
- </p>
-</div>
-
-<div>
- <div class="flex justify-between items-center">
- <span class="font-bold">{$t("user.settings.publishNip5")}</span>
- <Toggle id="nip5" bind:value={user.nip5} />
- </div>
- <p class="text-secondary mt-1 w-9/12">
- {$t("user.settings.nip5a")}
- <span class="font-bold">{user.username}@{$page.url.host}</span>
- {$t("user.settings.nip5b")}
- </p>
-</div>
-
-<div>
- <div class="flex justify-between items-center">
- <span class="font-bold">{$t("user.settings.autoWithdraw")}</span>
- <Toggle id="autowithdraw" bind:value={user.autowithdraw} />
- </div>
- <p class="text-secondary mt-1 w-9/12">
- {$t("user.settings.autoWithdrawDescription")}
- </p>
-</div>
-
-{#if user.autowithdraw}
- <div class="mb-2">
- <label for="display" class="font-bold mb-1 block"
- >{$t("user.settings.destination")}</label
- >
- <textarea
- name="destination"
- placeholder={$t("user.settings.destinationPlaceholder")}
- on:keypress={keypress}
- class="w-full p-4 border rounded-xl h-48"
- bind:value={user.destination}
- />
- </div>
-
- <div>
- <label for="display" class="font-bold mb-1 block"
- >{$t("user.settings.threshold")}</label
- >
- <button type="button" class="flex w-full" on:click={editThreshold}>
- <div class="p-4 border rounded-2xl rounded-r-none border-r-0 bg-primary">
- ⚡️
- </div>
- <div
- class="border-l-0 rounded-l-none pl-2 w-full p-4 border rounded-2xl text-left"
- >
- {user.threshold}
- </div>
- <input type="hidden" name="threshold" bind:value={user.threshold} />
- </button>
- <p class="text-secondary mt-1">
- {$t("user.settings.thresholdDesc")}
- </p>
- </div>
-
- <div>
- <label for="display" class="font-bold mb-1 block"
- >{$t("user.settings.reserve")}</label
- >
- <button type="button" class="flex w-full" on:click={editReserve}>
- <div class="p-4 border rounded-2xl rounded-r-none border-r-0 bg-primary">
- ⚡️
- </div>
- <div
- class="border-l-0 rounded-l-none pl-2 w-full p-4 border rounded-2xl text-left"
- >
- {user.reserve}
- </div>
- <input type="hidden" name="reserve" bind:value={user.reserve} />
- </button>
- <p class="text-secondary mt-1">
- {$t("user.settings.reserveDesc")}
- </p>
- </div>
-{/if}
-
-{#if editingThreshold}
- <div
- class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
- >
- <div
- class="relative top-40 mx-auto p-5 border shadow-lg rounded-md bg-white space-y-5 w-lg max-w-lg"
- >
- <h1 class="text-center text-2xl font-semibold">
- {$t("user.settings.threshold")}
- </h1>
- <Numpad
- bind:amount={user.threshold}
- bind:currency
- bind:rate
- bind:submit={doneReserve}
- bind:element={thresholdEl}
- />
-
- <button
- bind:this={doneReserve}
- type="button"
- on:click={doneEditing}
- class="p-4 bg-black text-white rounded-2xl text-lg font-semibold w-full"
- >Ok</button
- >
- </div>
- </div>
-{/if}
-
-{#if editingReserve}
- <div
- class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
- >
- <div
- class="relative top-40 mx-auto p-5 border shadow-lg rounded-md bg-white space-y-5 w-lg max-w-lg text-center"
- >
- <h1 class="text-2xl font-semibold">
- {$t("user.settings.reserve")}
- </h1>
- <Numpad
- bind:amount={user.reserve}
- bind:currency
- bind:rate
- bind:submit={doneReserve}
- bind:element={reserveEl}
- />
- <button
- bind:this={doneReserve}
- type="button"
- on:click={doneEditing}
- class="p-4 bg-black text-white rounded-2xl text-lg font-semibold w-full"
- >Ok</button
- >
- </div>
- </div>
-{/if}
-
-<a href="./shopify">
- <button
- type="button"
- class="flex rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full gap-2 my-5 justify-center"
- >
- <img src="/icons/shopify.svg" class="w-8" />
- <div class="my-auto">{$t("user.settings.shopify")}</div>
- </button>
-</a>
diff --git a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte
deleted file mode 100644
index 6b44f830..00000000
--- a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte
+++ /dev/null
@@ -1,321 +0,0 @@
-<script>
- import { getMnemonic, getNsec } from "$lib/nostr";
- import { tick } from "svelte";
- import { t } from "$lib/translations";
- import Pin from "$comp/Pin.svelte";
- import Icon from "$comp/Icon.svelte";
- import Qr from "$comp/Qr.svelte";
- import { copy, post, success, fail } from "$lib/utils";
- import { pin as current } from "$lib/store";
- import { invalidate } from "$app/navigation";
- import { page } from "$app/stores";
-
- export let user, rates, submit;
-
- let confirming2fa,
- disabling2fa,
- importing,
- locked,
- mnemonic,
- newNsec,
- nsec,
- old,
- otp,
- password,
- pin,
- revealPassword,
- revealSeed,
- revealNsec,
- token = "",
- setting2fa,
- settingPin,
- verify;
-
- let toggleImporting = () => {
- revealNsec = false;
- importing = !importing;
- };
-
- let toggleNsec = async () => {
- try {
- nsec = await getNsec(user);
- revealNsec = !revealNsec;
- importing = false;
- } catch (e) {
- console.log(e);
- }
- };
-
- let togglePassword = () => (revealPassword = !revealPassword);
- let toggleSeed = async () => {
- try {
- mnemonic = await getMnemonic(user);
- revealSeed = !revealSeed;
- } catch (e) {
- console.log(e);
- }
- };
-
- $: verifying = pin?.length > 5;
- $: verify && checkPin(pin);
- let checkPin = async () => {
- old = $current;
-
- try {
- if (pin.length > 5 && pin === verify) {
- user.haspin = true;
- $current = pin;
- pin = "";
- verify = "";
- submit.click();
- settingPin = false;
- verifying = false;
- } else {
- fail("Pin mismatch, try again");
- pin = "";
- verify = "";
- verifying = false;
- settingPin = true;
- }
- } catch (e) {
- console.log(e);
- fail("Problem setting PIN");
- }
- };
-
- let reset = () => {
- token = "";
- return true;
- };
-
- let disablingPin = false;
-
- let togglePin = async () => {
- if (user.haspin) {
- try {
- disablingPin = true;
- await tick();
- submit.click();
- } catch (e) {
- console.log(e);
- fail("Failed to disable pin");
- }
- } else {
- settingPin = true;
- disablingPin = false;
- }
- };
-
- let startEnabling2fa = async () => {
- reset();
- try {
- if (!otp) otp = await post("/otpsecret", { pin: $current });
- } catch (e) {
- console.log(e);
- }
-
- setting2fa = true;
- };
-
- let startDisabling2fa = () => (disabling2fa = true);
- let startConfirming2fa = () => (confirming2fa = true);
- let cancel = () => {
- pin = null;
- token = null;
- verifying = false;
- settingPin = false;
- setting2fa = false;
- confirming2fa = false;
- disabling2fa = false;
- };
-
- $: enable2fa(token);
- let enable2fa = async (twoFa) => {
- try {
- if (setting2fa && token.length === 6) {
- await post("/enable2fa", { token });
- success("2FA enabled");
- user.twofa = 1;
- cancel();
- }
- } catch (e) {
- fail("Failed to enable 2FA, try again");
- }
- };
-
- $: disable2fa(token);
- let disable2fa = async () => {
- try {
- if (disabling2fa && token.length === 6) {
- await post("/disable2fa", { token });
- success("2FA disabled");
- delete user.twofa;
- disabling2fa = false;
- }
- } catch (e) {
- console.log(e);
- fail("Failed to disable 2FA, try again");
- }
- };
-</script>
-
-<input type="hidden" name="newpin" value={disablingPin ? "delete" : pin} />
-<input type="hidden" name="confirm" bind:value={password} />
-
-<div class="relative mb-5">
- <label for="password" class="block font-bold block mb-1"
- >{$t("user.settings.newPassword")}</label
- >
- {#if revealPassword}
- <input name="password" type="text" bind:value={password} />
- {:else}
- <input name="password" type="password" bind:value={password} />
- {/if}
- <button
- type="button"
- on:click={togglePassword}
- on:keydown={togglePassword}
- class="absolute right-5 top-11"
- >
- <Icon icon={revealPassword ? "eye" : "eye-off"} />
- </button>
-</div>
-
-<div>
- <span class="font-bold mb-1"
- >{verifying
- ? $t("user.settings.verifyPIN")
- : $t("user.settings.securityPIN")}</span
- >
- <p class="text-secondary mb-1">
- {$t("user.settings.securityPINDescription")}
- </p>
- {#if verifying}
- <Pin bind:value={verify} {cancel} notify={false} />
- {:else if settingPin}
- <Pin
- bind:value={pin}
- title={$t("user.settings.setPIN")}
- {cancel}
- notify={false}
- />
- {:else}
- <button type="button" class="primary" on:click={togglePin}
- ><Icon icon="lock" style="mr-1" />
- {user.haspin
- ? $t("user.settings.disablePIN")
- : $t("user.settings.enablePIN")}</button
- >
- {/if}
-</div>
-
-<div>
- <span class="font-bold mb-1">{$t("user.settings.twofa")}</span>
- <p class="text-secondary mb-4">
- {$t("user.settings.twofaDescription")}
- </p>
-
- {#if setting2fa}
- <a href={otp.uri}>
- <Qr src={otp.qr} />
- </a>
-
- <div class="text-center my-4">
- {$t("user.settings.accountId")}<br />
- <b>{otp.secret}</b>
- </div>
-
- <button type="button" class="primary" on:click={startConfirming2fa}>
- <Icon icon="numpad" style="w-8 mr-1" />
- <div class="my-auto">Confirm</div>
- </button>
- {:else if user.twofa}
- <button type="button" class="primary" on:click={startDisabling2fa}>
- <Icon icon="mobile" style="w-8 mr-1" />
- <div class="my-auto">{$t("user.settings.twofaDisable")}</div>
- </button>
- {:else}
- <button type="button" class="primary" on:click={startEnabling2fa}>
- <Icon icon="mobile" style="w-8 mr-1" />
- <div class="my-auto">{$t("user.settings.twofaSetup")}</div>
- </button>
- {/if}
-
- {#if confirming2fa || disabling2fa}
- <Pin
- bind:value={token}
- title="Enter 2FA Code"
- {cancel}
- persist={false}
- notify={false}
- />
- {/if}
-</div>
-
-<!-- <div> -->
-<!-- <label for="seedphrase" class="font-bold" -->
-<!-- >{$t("user.settings.seedPhrase")}</label -->
-<!-- > -->
-<!-- -->
-<!-- <p class="text-secondary mb-1"> -->
-<!-- {$t("user.settings.seedDescription")} -->
-<!-- </p> -->
-<!-- -->
-<!-- <button type="button" class="primary" on:click={toggleSeed}> -->
-<!-- <Icon icon="warning" style="mr-1 w-6 my-auto" /> -->
-<!-- {revealSeed ? $t("user.settings.hideSeed") : $t("user.settings.revealSeed")} -->
-<!-- </button> -->
-<!-- -->
-<!-- {#if revealSeed} -->
-<!-- <div class="text-lg"> -->
-<!-- {mnemonic} -->
-<!-- </div> -->
-<!-- -->
-<!-- <button type="button" class="primary" on:click={() => copy(mnemonic)}> -->
-<!-- <Icon icon="copy" style="mr-1 w-6 my-auto" /> -->
-<!-- Copy -->
-<!-- </button> -->
-<!-- {/if} -->
-<!-- </div> -->
-
-<div>
- <label for="seedphrase" class="font-bold"
- >{$t("user.settings.nostrKeys")}</label
- >
-
- <p class="text-secondary mb-1">
- {$t("user.settings.nostrDescription")}
- </p>
-
- <div class="flex">
- <button type="button" class="primary" on:click={toggleNsec}>
- {#if revealNsec}
- <Icon icon="eye-off" style="mr-1 w-6 my-auto" />
- {$t("user.settings.hideNsec")}
- {:else}
- <Icon icon="warning" style="mr-1 w-6 my-auto" />
- {$t("user.settings.revealNsec")}
- {/if}
- </button>
-
- <button type="button" class="primary" on:click={toggleImporting}>
- <Icon icon="save" style="mr-1 w-6 my-auto" />
- {$t("user.settings.import")}
- </button>
- </div>
-
- {#if importing}
- <input name="newNsec" bind:value={newNsec} placeholder="nsec..." />
- {/if}
-
- {#if revealNsec}
- <div class="text-lg break-all">
- {nsec}
- </div>
-
- <button type="button" class="primary" on:click={() => copy(nsec)}>
- <Icon icon="copy" style="mr-1 w-6 my-auto" />
- Copy
- </button>
- {/if}
-</div>
diff --git a/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte b/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte
deleted file mode 100644
index 0266fa9c..00000000
--- a/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte
+++ /dev/null
@@ -1,92 +0,0 @@
-<script>
- import { t } from "$lib/translations";
- import Icon from "$comp/Icon.svelte";
- import { page } from "$app/stores";
-
- export let user;
- $: paymentsUrl =
- user.shopifyStore &&
- "https://admin.shopify.com/store/" +
- user.shopifyStore +
- "/settings/payments";
- $: checkoutUrl =
- user.shopifyStore &&
- "https://admin.shopify.com/store/" +
- user.shopifyStore +
- "/settings/checkout";
- $: appsUrl =
- user.shopifyStore &&
- "https://admin.shopify.com/store/" +
- user.shopifyStore +
- "/settings/apps/development";
-
- let script = `<script src="${$page.url.protocol}//${$page.url.host}/${user.username}/shopify.js"><\/script>`;
-</script>
-
-<div>
- <label for="shopifyStore" class="font-bold mb-1 block">Store ID</label>
- <div class="flex mb-2">
- <div
- class="text-gray-600 p-4 my-auto bg-gray-100 border rounded-l-2xl border-r-0 pr-1"
- >
- admin.shopify.com/store/
- </div>
- <input
- type="text"
- name="shopifyStore"
- bind:value={user.shopifyStore}
- class="border-l-0 rounded-l-none w-auto min-w-0 pl-1 grow"
- />
- </div>
- <p class="text-secondary my-2">
- Add Bitcoin under <a
- class:active={!!paymentsUrl}
- href={paymentsUrl || undefined}>Payments > Manual payment methods</a
- > with a description like "Pay with Bitcoin".
- </p>
-</div>
-
-<div>
- <label for="display" class="font-bold mb-1 block">Additional scripts</label>
- <p class="text-secondary my-2">
- Copy this into <a class:active={!!paymentsUrl} href={checkoutUrl}
- >Checkout -> Additional scripts</a
- >
- </p>
- <textarea rows={5} type="text" name="shopifyScripts" readonly
- >{script}</textarea
- >
-</div>
-
-<div>
- <label for="shopifyToken" class="font-bold mb-1 block"
- >Admin API token <span class="font-normal">(Optional)</span></label
- >
- <p class="text-secondary my-2">
- Coinos can automatically mark orders as Paid if you provide an API token.
- <input
- class="my-4"
- type="text"
- name="shopifyToken"
- bind:value={user.shopifyToken}
- />
- </p>
- <p class="text-secondary my-2">
- Create a new app in <a class:active={!!appsUrl} href={appsUrl || undefined}
- >Apps > App Development</a
- >
- </p>
-
- <p class="text-secondary my-2">
- Edit the Admin API scopes to enable <code class="text-black"
- >write_orders</code
- > permission then install the app into your store and copy the Admin API access
- token here.
- </p>
-</div>
-
-<style>
- .active {
- @apply text-blue-500;
- }
-</style>
diff --git a/src/routes/(app)/[username]/verify/[code]/+page.server.ts b/src/routes/(app)/[username]/verify/[code]/+page.server.ts
deleted file mode 100644
index fec1a85c..00000000
--- a/src/routes/(app)/[username]/verify/[code]/+page.server.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { get } from "$lib/utils";
-import { redirect } from "@sveltejs/kit";
-
-export let load = async ({ params: { code } }) => {
- let user;
- try {
- user = await get(`/verify/${code}`);
- } catch (e) {
- console.log(e);
- return { error: "Verification failed" };
- }
-
- redirect(307, `/${user.username}/settings/account?verified=true`);
-};
diff --git a/src/routes/(app)/[username]/verify/[code]/+page.svelte b/src/routes/(app)/[username]/verify/[code]/+page.svelte
deleted file mode 100644
index 1e099dae..00000000
--- a/src/routes/(app)/[username]/verify/[code]/+page.svelte
+++ /dev/null
@@ -1,35 +0,0 @@
-<script>
- import { scale } from "svelte/transition";
- import Icon from "$comp/Icon.svelte";
- import { t } from "$lib/translations";
-
- export let data;
- let { user, error } = data;
-</script>
-
-<div class="container px-4 text-center mx-auto">
- {#if error}
- <h1 class="text-3xl md:text-4xl font-bold mb-6">
- Email verification failed
- </h1>
- {:else if user.verified}
- <div
- class="flex w-full py-5 max-w-[200px] mx-auto"
- in:scale={{ start: 0.5 }}
- >
- <Icon icon="check" style="mx-auto" />
- </div>
-
- <h1 class="text-3xl md:text-4xl font-bold mb-6">
- Email verified successfully!
- </h1>
- {/if}
-</div>
-
-<a href={`/${user.username}`}>
- <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" />
-</a>
-
-<div class="fixed bottom-10 w-full text-center">
- {$t("payments.tapAnywhere")}
-</div>